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.
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.
npm i @pushary/claude-agent-sdk @anthropic-ai/claude-agent-sdkimport { 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.
- 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.
- 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.
- 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.
- 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.
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?
Which tool names do I allow for the MCP server?
Does this work in the Python Claude Agent SDK?
Who answers the question?
What if nobody replies before the window closes?
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 freePartner plan, $99 a month. Your users never make a Pushary account or pay us.