Human-in-the-loop for the Claude Agent SDK

Human-in-the-loop for the Claude Agent SDK, in one canUseTool.

When your Claude agent wants a tool that needs permission, your user taps Approve or Deny on their phone.

What Claude Agent SDK gives you

The canUseTool callback

The Claude Agent SDK lets you check a tool call before it runs. Every tool that needs permission passes through your canUseTool callback first, where you return allow or deny and can rewrite the input.

native
const result = query({
  prompt,
  options: {
    canUseTool: async (toolName, input) => {
      // your decision goes here
      return { behavior: "allow", updatedInput: input }
    },
  },
})

Where it stops

  • The callback answers inside your process. It cannot reach a person away from that machine.
  • It must answer before the SDK stops waiting, so a long human wait needs handling outside it.
  • Allow and deny leave no trail. Nothing adds up to a record you can search later.

The fix

Pushary gets your user's answer.

canUseTool decides whether a tool runs. pusharyCanUseTool fills it: the question goes to your user's phone, their tap comes back as allow or deny, and every answer is saved.

Pass pusharyCanUseTool() with your own user id in the options of query(). Approve runs the tool with its input unchanged. Deny, or no answer in time, returns a denial the model can read. Claude's AskUserQuestion questions go to the phone too, as choices.

bash
npm i @pushary/claude-agent-sdk @anthropic-ai/claude-agent-sdk
one option
import { query } from "@anthropic-ai/claude-agent-sdk"
import { pusharyCanUseTool } from "@pushary/claude-agent-sdk"

for await (const message of query({
  prompt: "Refund order 1234",
  options: { canUseTool: pusharyCanUseTool({ externalId: user.id }) },
})) {
  if (message.type === "result") console.log(message.subtype)
}

How it works

From install to the first answer

Connect your user once. Put the approval on the risky step. Pushary sends the question, waits for the answer and saves it.

  1. 01

    Install the adapter

    Run npm i @pushary/claude-agent-sdk @anthropic-ai/claude-agent-sdk and set PUSHARY_API_KEY to the key from Partner onboarding.

  2. 02

    Connect each user's phone once

    connect() returns a link. Your user opens it once on their phone and installs the free Pushary app. No Pushary account, no payment.

  3. 03

    Pass the gate to query()

    Set canUseTool to pusharyCanUseTool() with your own user id. Take the id from your signed-in user, never from the tool input, because the model writes the tool input.

  4. 04

    Act on the answer

    Approve runs the tool unchanged. Deny, no answer or a cancelled run returns a denial with a reason, and the tool does not run.

A question can be a yes or no, a choice from a list, or a typed answer. Every answer is saved.

Before you ship

Things that bite on Claude Agent SDK

Your Pushary rules are off unless you turn them on
Claude's tools share names with your own Claude Code: Bash, Read, Write. A rule written for your own sessions would also answer your users' calls, so by default every gated call goes to a person. Set policy: true only for rules you wrote for this agent.
Denying without a reason confuses the model
A deny with no reason often makes the agent try a slightly different command to reach the same place. Return a reason so the model knows a person decided, rather than the call being malformed, and stops rephrasing.
Match on the input, not just the tool name
Bash is one tool name that covers git status and rm -rf. Gate on the name alone and you either interrupt constantly or let through the call you cared about. The decision has to read the arguments.
Subagents call tools too
Work handed to a subagent still runs tools. If you picture one callback for one straight session, the calls from delegated work can surprise you.

The Partner plan

Your agent asks your users, not just you

Ship your agent to your users. Each of them approves its risky actions from their phone, under your brand.

$99/ month, every framework included
Start free

Your users never make a Pushary account or pay us.

Your users answer on their own phones

Connect each user once with a link. They install the free Pushary app and answer from the lock screen, with no account of their own.

No answer means no

Run the action only after an explicit Approve. A request that is still waiting, expired or cancelled never counts as a yes. Pushary keeps every request, so your code can pick it up again after a restart.

Reaches them on their phone

The app comes first, because only the app puts Approve and Deny on the lock screen. Browser notifications come next. You build no delivery system.

Every answer saved

Every answer is saved with who answered and when. You can search and export it, and with a callback URL, each answer is also sent to your server as a signed webhook.

FAQ

Questions, answered

Do I need a Pushary npm package for the Claude Agent SDK?
To gate tool calls on your users, yes: @pushary/claude-agent-sdk is the canUseTool, and it sends each call that needs permission to your user's phone. Tools you allow with allowedTools never reach it. For your own agents, the hosted MCP server needs no install: one mcpServers entry plus allowedTools.
Which tool names do I allow for the MCP server?
mcp__pushary__ask_user, mcp__pushary__wait_for_answer, mcp__pushary__send_notification and mcp__pushary__cancel_question. The prefix is mcp, then the server name, then the tool. Put the server in mcpServers with your API key as a Bearer token.
Does this work in the Python Claude Agent SDK?
The adapter is TypeScript only. In Python, pass the hosted MCP server in ClaudeAgentOptions(mcp_servers={...}) with the Bearer header and allow the same tool names, or call decisions.ask from pip install pushary inside your own can_use_tool.
Who answers the question?
With the adapter, the person whose user id you pass, which is your own id for them. They connect their phone once with the link from connect(). You can also pass a function that picks a person per call. Over MCP, the phones connected to your Pushary workspace answer.
What if nobody replies before the window closes?
The tool does not run. The gate denies and the model gets a denial it can read. Cancelling the run stops the wait and denies at once.

Let your Claude Agent SDK agent ask before it acts.

Connect a user once, ask with one call, and run the action only on a yes. Every answer is saved.

Start free

Partner plan, $99 a month. Your users never make a Pushary account or pay us.