[BUG] PreToolUse `tool_input` differs from the transcript's `tool_use.input` when the tool schema strips a key (Agent `run_in_background` under the fork-subagent gate)

Status Open
Reported on v2.1.292
Maintainer reply None cached
Activity 0 comments · opened Oct 7, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet (closest: #91225, which is the fork gate overriding foreground; this report is about the hook/transcript input mismatch it causes)
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.292, macOS)

What's Wrong?

The session transcript (~/.claude/projects/<proj>/<session>.jsonl) records the model's raw tool input, but PreToolUse hooks receive the schema-parsed input. When the schema drops or coerces a key, the two disagree, so a hook that cross-checks the call it is gating against the transcript (the documented transcript_path field) cannot match them.

Concrete case: in 2.1.292 the Agent schema omits run_in_background when epr() || MZ() holds, meaning background launch is restricted or the fork-subagent gate is on (MZ(), default ON). If the model still sends the key:

| Where | Agent input keys |
|---|---|
| Transcript tool_use.input | description, prompt, subagent_type, run_in_background (value: the string "false") |
| PreToolUse tool_input | description, prompt, subagent_type |

Observed in 7 of 7 such calls across two real sessionKind: "bg" sessions, and reproduced in nested --bg probe sessions running a logging PreToolUse hook. I have not captured the payload in a foreground session with only the fork gate on. The schema code path is the same one.

With CLAUDE_CODE_FORK_SUBAGENT=false set (settings env, after a restart) the key is in the schema, the call runs in the foreground, and the transcript records a real boolean false, matching the hook payload.

What Should Happen?

PreToolUse tool_input and the transcript's tool_use.input should describe the same call. Any of these would fix it:

  1. Write the parsed input (what actually runs) to the transcript, or record both.
  2. Give the hook the raw input too, e.g. tool_input_raw.
  3. Reject unknown keys with a validation error instead of stripping them silently. The model would then learn the parameter isn't there.

Steps to Reproduce

  1. Claude Code 2.1.292 in a session whose Agent schema omits run_in_background. Observed in a --bg session; per the schema code, the default fork-subagent gate should produce the same omission.
  2. Register a PreToolUse hook on Agent that logs tool_input to a file and allows the call.
  3. Ask Claude to call Agent with run_in_background: false. It may emit the string "false".
  4. Compare the hook's logged tool_input with that tool_use record in the transcript. The hook copy is missing run_in_background; the transcript has it.

Impact

Third-party guards that bind a tool call to the transcript deny every such call with no way for the user to recover. gstack's /autoplan publication hook denied every reviewer dispatch across three attempts (garrytan/gstack#3062).

Related (separate bugs, not filed here)

  • When the key is omitted in a sessionKind: "bg" session, the turn's tool_use record often isn't flushed to the transcript until the PreToolUse hook returns: 1 of 6 within 20 s in probes, versus about 110 ms when the key is present. So a hook that waits for the record can never see it.
  • #91225: the fork gate overriding run_in_background: false (2.1.252).

Claude Code Version

2.1.292 (macOS, Ghostty terminal)

View original on GitHub ↗