PostToolUse `updatedToolOutput` silently ignored for built-in tools (Bash, WebFetch) on v2.1.173
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
- 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}))
- Create
.claude/settings.json:
``json``
{ "hooks": { "PostToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 hook.py" } ] }
] } }
- 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."
- Expected: the model reports
REPLACED_BY_HOOK. Actual: it reportsPROBE_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-firing —
additionalContextandpermissionDecision: "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 —
updatedToolOutputfor built-in tools - #47853, #39814 — parallel reports of
updatedInputsilently 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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗