PostToolUse `updatedToolOutput` silently ignored for built-in tools (Bash, WebFetch) on v2.1.173

Resolved 💬 2 comments Opened Jun 11, 2026 by vadminas Closed Jun 14, 2026

Environment

  • Claude Code version: 2.1.173
  • OS: Linux (Ubuntu, LXD container)
  • Hook type: PostToolUse

Summary

A PostToolUse hook that returns hookSpecificOutput.updatedToolOutput has no effect — the model still receives the original, unmodified tool result. This contradicts the documented behavior (per the changelog, updatedToolOutput for built-in tools shipped in v2.1.121). Other hook outputs from the same hook work correctly (additionalContext is honored; permissionDecision: "deny" blocks), so the hook is firing and its JSON is being parsed — only updatedToolOutput is dropped. Reproduces for both Bash and WebFetch.

Expected behavior

Per the hooks reference, returning:

{ "hookSpecificOutput": { "hookEventName": "PostToolUse", "updatedToolOutput": "<replacement>" } }

should replace the tool result the model sees with <replacement>.

Actual behavior

The model receives the original tool output; <replacement> is never seen.

Minimal reproduction

  1. In a scratch dir, create hook.py:

``python
import sys, json
d = json.load(sys.stdin)
cmd = (d.get("tool_input") or {}).get("command", "")
out = {"hookEventName": "PostToolUse"}
if "PROBE_MARKER" in cmd:
out["updatedToolOutput"] = "REPLACED_BY_HOOK"
print(json.dumps({"hookSpecificOutput": out}))
``

  1. Create .claude/settings.json:

``json
{ "hooks": { "PostToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 hook.py" } ] }
] } }
``

  1. From that dir, run:

``
claude -p "Run exactly this bash command and nothing else: echo PROBE_MARKER_ORIGINAL_TEXT . Then reply with ONLY the exact verbatim text of the tool result you received."
``

  1. Expected: the model reports REPLACED_BY_HOOK. Actual: it reports PROBE_MARKER_ORIGINAL_TEXT.

What I ruled out

  • Not a wrong field/shape — exact field name and JSON shape per the docs; the hook emits valid JSON and exits 0 (confirmed via a trace log).
  • Not hook-not-firingadditionalContext and permissionDecision: "deny" from the same hook both work, proving the harness reads the hook's output.
  • Not multi-hook interference (cf. #15897) — reproduced with a single PostToolUse hook registered.
  • Not WebFetch-specific — also ignored for Bash (the repro above). So it appears broadly broken for built-in tools, not just one.

Related

  • #32105, #36843 — updatedToolOutput for built-in tools
  • #47853, #39814 — parallel reports of updatedInput silently ignored for specific tools (Edit, Agent)

Impact

This is the only mechanism to transform a tool's output from a hook. The motivating use case: wrapping WebFetch/WebSearch results in an "untrusted content" trust boundary so fetched web pages reach the model as data rather than instructions (prompt-injection defense). With updatedToolOutput ignored, raw WebFetch output cannot be sanitized at the hook layer at all — the most-used fetch path is left unwrappable.

View original on GitHub ↗

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