No. 002

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.

By Staso Engineering3 min read
  • Claude Code
  • Hooks
  • Guardrails

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

JobBoundary to inspectEvidence to expect
Understand what the model proposedBefore tool executionTool name and available arguments
Decide whether a supported proposal may proceedAn inline PreToolUse checkA decision before dispatch
Inspect a completed toolAfter tool executionExposed result or failure information
Inspect the finished turnTurn completionFinalized 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-code

Setup 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

ObservationCheck
A would-block finding exists, but the action continuedThe policy may be in Audit, where continuation is expected
A trace exists, but no Guard finding appearsCheck that Guard is enabled and an enabled policy selects the rule and matches the call
A rule skipped or abstainedCheck the evidence it requires; missing arguments or schema are not a rule match
Guard could not return a decisionInspect availability and the configured fail-open or fail-closed behavior
A custom background hook reported a failure laterCheck 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.

Subscribe

Get the next post in your inbox.

One email when we publish a new guide to agent tracing, hooks, or tool-call guardrails.

We'll email you only when we publish.