PreToolUse `updatedInput` from an `allow` hook is silently discarded when a sibling hook returns `ask` and the user approves

Resolved 💬 2 comments Opened Jul 8, 2026 by 0rangeSeaW0lf Closed Jul 12, 2026

Environment

  • Claude Code 2.1.204
  • macOS (darwin)
  • Two PreToolUse hooks registered on the same matcher in settings.json

Summary

When two PreToolUse hooks match the same tool call, and hook A returns permissionDecision: "allow" with updatedInput while hook B returns permissionDecision: "ask", approving the resulting permission prompt executes the ORIGINAL tool input. Hook A's updatedInput mutation is dropped, and neither the user nor hook A gets any signal that this happened. In effect, one hook asking for confirmation silently cancels another hook's input rewrite.

Reproduction

  1. Register two PreToolUse hooks on the same matcher (reproduced on an MCP tool matcher):

hook-a.sh (mutating hook):

``json
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow", "permissionDecisionReason": "content rewrite", "updatedInput": {"...": "tool_input with one field modified"}}}
``

hook-b.sh (confirming hook):

``json
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "ask", "permissionDecisionReason": "please confirm"}}
``

Registered together on one matcher:

``json
{"hooks": {"PreToolUse": [{"matcher": "<your tool matcher>", "hooks": [
{"type": "command", "command": "/path/hook-a.sh"},
{"type": "command", "command": "/path/hook-b.sh"}
]}]}}
``

  1. Trigger a matching tool call whose input hook A will modify.
  2. Approve the permission prompt raised by hook B.
  3. Inspect what actually executed: the tool's side effect (e.g. content stored by the call) and the PostToolUse tool_input both carry the ORIGINAL input, not hook A's updatedInput.

Control arm: remove hook B (so no ask fires) and repeat. Hook A's updatedInput IS applied: the side effect and PostToolUse tool_input carry the modified input.

Verified via a controlled A/B on the same payload, comparing SHA-1 digests of the content the tool actually persisted: no-ask arm stored the modified bytes; ask-plus-approval arm stored the original bytes.

Expected

Either of these would be consistent behavior:

  • The approved call executes with the sibling updatedInput applied (approval approves the call, mutations still compose), or
  • The documentation for multi-hook precedence (deny > defer > ask > allow) states explicitly that when ask wins and the user approves, sibling updatedInput mutations are discarded.

Actual

The original input executes silently. The mutating hook has already reported success (its own stdout said allow + updatedInput), so from its perspective the mutation happened. There is no warning, no log line, and no way for hook A to detect the drop at PreToolUse time.

Impact

Content-rewriting hooks (sanitizers, scrubbers, redactors) silently fail exactly when another hook asks for confirmation, which in practice correlates with the higher-risk calls. If the mutation is a policy control, this is a silent policy bypass: telemetry from the mutating hook claims the rewrite happened while the unmodified payload goes through.

At minimum this looks like a documentation gap on the multi-hook precedence section; ideally updatedInput from an allow-voting hook would survive an approved ask.

Related: #66203 asks for the multi-hook semantics (execution order, updatedInput visibility, conflict resolution) to be specified in general. This report is one specific, reproduced data point for that specification: ask-wins-then-approve currently discards sibling updatedInput silently.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗