Claude Code async hooks and tool-call blocking
Choose the right hook boundary for Claude Code tracing and blocking. Understand background execution, inline checks, and the evidence each produces.
An asynchronous Claude Code hook lets the agent continue while the hook works in the background. That makes it useful for work that can finish later. A decision that must stop a tool call needs to complete before that tool proceeds.
The official Claude Code hooks reference makes this boundary explicit: background hook decision fields cannot block or control an action that has already continued. A successful background job is therefore not evidence that a tool was prevented from running.
Choose the moment that matches the job
| Job | Boundary to inspect | Evidence to expect |
|---|---|---|
| Understand what the model proposed | Before tool execution | Tool name and available arguments |
| Decide whether a supported proposal may proceed | An inline PreToolUse check | A decision before dispatch |
| Inspect a completed tool | After tool execution | Exposed result or failure information |
| Inspect the finished turn | Turn completion | Finalized turn and available usage |
These are different facts. A proposal says what the agent requested. A post-tool event supplies evidence about what happened next. A final answer can omit both the intermediate failure and the retry that followed it.
For a repeated-call investigation, compare the proposed arguments with the results of the actual executions. A repeated tool name alone cannot tell you whether the agent is retrying a failed request, progressing through different inputs, or stuck.
Keep an enforcement decision on the call path
If your rule needs to stop a proposed action, the host must receive its decision before dispatch. Moving the decision into background work removes that ordering guarantee.
For Staso's supported Claude Code path, PreToolUse is the Guard check. The check requires Guard to be enabled and a matching policy to be configured. Audit records a would-block finding while the proposal continues. Enforce can stop a matching supported proposal before execution, where the account plan allows that mode.
Leave the generated Guard hook configuration intact when you need this behavior. Tune the policy's scope to the tools you need to review. Inline evaluation adds latency based on enabled rules, model calls, and the network; measure that latency with your configuration.
Trace a small task before testing a policy
With Python 3.11 or newer, configure the Staso hooks:
python3 -m pip install --upgrade staso
staso setup --target claude-code
staso status --target claude-codeSetup prompts for a workspace API key. Global setup writes hooks to ~/.claude/settings.json; --scope project writes .claude/settings.local.json in the current project. Staso keeps the key in its credential storage. Review the settings before starting a task.
Ask Claude Code to read a harmless project file and explain it. Open Traces > Traces in the matching workspace, then inspect the prompt and any exposed tool steps. A real event after setup is necessary; setup itself does not manufacture a trace.
Staso records supported host events. Model and token detail depend on what Claude Code exposes. The hook does not supply the host's system prompt or exact tool schema, so do not treat those missing fields as a capture failure.
Diagnose a call that was not blocked
| Observation | Check |
|---|---|
| A would-block finding exists, but the action continued | The policy may be in Audit, where continuation is expected |
| A trace exists, but no Guard finding appears | Check that Guard is enabled and an enabled policy selects the rule and matches the call |
| A rule skipped or abstained | Check the evidence it requires; missing arguments or schema are not a rule match |
| Guard could not return a decision | Inspect availability and the configured fail-open or fail-closed behavior |
| A custom background hook reported a failure later | Check whether any inline decision actually ran before dispatch |
Staso fails open by default when no policy decision is available. Fail-closed is an explicit configuration choice. An unavailable decision that stops execution is an operational failure, not a matched rule. Keep it separate when reviewing policy precision.
Review the trace and the policy together
Start with an audit policy scoped to one agent, environment, and tool. Inspect the current proposal and the evidence behind each finding. Only switch to enforcement after you understand which calls match and which legitimate calls would be interrupted.
Capture controls also affect that evidence. Suppressed arguments cannot be sent to a Guard rule, and the current tool's result does not exist before execution. The rules catalog explains which inputs each published rule category needs.
If your own Python application dispatches tools, its boundary is different from the CLI hook path. Use the direct-call Guard example to put the decision before the function runs.
Get the next post in your inbox.
One email when we publish a new guide to agent tracing, hooks, or tool-call guardrails.