Feature request: give PreToolUse hooks visibility into the in-flight assistant message (or flush it to the transcript before hooks run)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 29, 2026

Problem

A PreToolUse hook cannot determine anything about the assistant message that contains the tool call it is evaluating. The message's content — in particular, whether the model emitted any visible text block before the tool call — is unavailable through every channel a hook has:

  1. The stdin payload carries session_id, transcript_path, cwd, prompt_id, permission_mode, effort, hook_event_name, tool_name, tool_input, and tool_use_id — no message content (dumped and verified live).
  2. The transcript JSONL at transcript_path receives the in-flight message's lines (thinking / text / tool_use, one line per content block sharing a message.id) only after PreToolUse hooks resolve. We verified this with a parse-aware poll inside the hook waiting for its own tool_use_id to appear: it timed out at 2503 ms and 2514 ms (registered hook timeout 5000 ms). The message lines land after the hook exits.
  3. The hook's environment (133 vars dumped) carries no message content either.

So from a PreToolUse hook's vantage point, "the model wrote four paragraphs of visible text and then called the tool" and "the model went straight from private thinking to the tool call" are byte-identical states.

One measurement trap worth documenting for anyone who retries this: polling the transcript for the call's own tool_use_id produces convincing false positives at ~100–200 ms, because sibling PreToolUse hooks' hook_success attachment lines echo the toolUseID and do land in the transcript promptly. Only a parse that requires an assistant line with a tool_use block of the exact id reveals that the actual message never arrives while hooks run.

Why it matters (concrete use case)

We run a guard hook on AskUserQuestion that denies the modal unless the model has emitted visible framing text since the user's last message. The failure it polices is real and recurrent: the model composes its framing in the thinking channel instead of a visible text block, then pops an unframed modal at the user. (Measured across our transcripts: in the sessions that produced user complaints, 12 of 13 modal calls carried zero text; the model cannot self-detect this — it sees its own thinking in context and reports in good faith that it "said" the framing. Only the transcript distinguishes the channels. In one debugging session the model slipped framing into thinking eight times while actively trying not to.)

The guard's correct behavior depends on exactly the bit PreToolUse cannot see: does the message carrying this tool call contain a visible text block? Because that bit is unavailable, the compliant pattern (text and modal in one message — the most natural way for a model to do this right) is indistinguishable from the violation, and the hook must either:

  • deny both and eat a bounce (the deny resolves the tool call, the message then flushes with its text, and the model's bare retry passes ~10 s later — our shipped workaround), or
  • allow both and miss the violation entirely.

Suggested solutions (any equivalent shape welcome — the problem statement is the ask)

Option A — expose the in-flight message (or a digest of it) in the PreToolUse payload. Full fidelity would be assistant_message: { id, content: [...] }. Our use case needs far less: current_message_block_types: ["thinking", "text", "tool_use"] or even a single current_message_has_visible_text: true|false would fully solve it, with no new privacy or size surface worth mentioning.

**Option B — append the in-flight assistant message's transcript lines before running PreToolUse hooks.** The blocks preceding the tool call are complete by then; hooks that scan the transcript would simply start seeing the current message. This also benefits any transcript-based PreToolUse tooling beyond our case.

Option C — anything else that lets a PreToolUse hook answer "did visible text precede this tool call in the same message?" If there's a better solution than A or B, we'd rather have that.

Environment

  • Claude Code 2.1.220, CLI entrypoint, Windows 11 (Git Bash)
  • Model: claude-fable-5, interleaved thinking, effort high
  • Timings above measured 2026-07-29 with a poll-instrumented hook; happy to share the instrumented hook and log excerpts.

View original on GitHub ↗