PostToolUse: malformed updatedToolOutput passes validation and throws 'e.reduce is not a function' instead of falling back to original output

Status Open
Reported on v2.1.226
Maintainer reply None cached
Activity 1 comment · opened Aug 10, 2026

Summary

A PostToolUse hook that returns a malformed updatedToolOutput (an object that matches no tool output shape, e.g. {"text": ""}) is not caught by the validation/fallback path the binary itself promises. Instead of degrading to the original tool output, the malformed value reaches a downstream content-length reducer and throws:

API Error: ... (In 'e.reduce((t,r)=>t+(r.type==="text"?r.text.length:0),0)', 'e.reduce' is undefined)

Every call to the affected MCP tool then hard-fails in live sessions.

Expected behavior

The binary's own validation message documents the intended contract:

"updatedToolOutput that does not match <tool>'s output shape: ... using original output."

i.e. a shape mismatch should be non-fatal: log, discard the hook's replacement, and fall back to the original tool output.

Actual behavior

The malformed value ({"text": ""} where an MCP tool's output shape is an array of content blocks) passes validation and then throws inside a reducer that assumes an array of content blocks (e.reduce(...) with e being the non-array object). The user-visible result is an API Error on every call to that tool - no fallback, no degradation.

Minimal repro

  1. Register any MCP server with any tool.
  2. Add a PostToolUse hook matching that tool which returns:
{"hookSpecificOutput":{"hookEventName":"PostToolUse","updatedToolOutput":{"text":""}}}
  1. Call the tool. Observed: e.reduce is not a function API error surfaced to the session instead of the original tool output.

Why it matters

In our case a small bug in our own hook (emitting {text: string} instead of content blocks) was converted by this gap into a weeks-long total outage of the MCP-backed feature - which is exactly the failure class the documented "using original output" fallback exists to prevent. The hook bug was ours; the missing fallback that amplified it is Claude Code's.

Environment

  • Observed and reproduced on Claude Code 2.x (current at filing: 2.1.226), Linux (WSL2) and Windows.
  • The validation string and the updatedToolOutput dispatch are present in the shipped binary (grep-able), so the intended non-fatal path exists - it just does not engage for this shape class.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗