Two hooks read tool_output / tool_result — the legacy
aliases. CC sends tool_response. Both saw an empty string on every
real payload, and both failed quietly. Their test suites were green the whole
time, because every case supplied the alias as well.
types.ts:58 literally says "legacy alias for tool_response".
| site | reads | on a real payload |
|---|---|---|
| mcp-output-transform.ts:306 | tool_response ?? tool_output ?? tool_result | correct already |
| token-estimator.ts:131 | { tool_result, tool_output, output } | '' → 0 tokens |
| secret-handler.ts:56 | tool_output ?? tool_result | '' → silent success |
The repo had already learned this once. The comment above
mcp-output-transform.ts:306 reads
"CC sends the result in tool_response (verified on CC 2.1.183) … reading
only those is why". The lesson was written down next to one call site and
never carried to the other two.
token-estimator '' ──▶ estimateTokens('') = 0
└─▶ cache-read accumulator never increments
├─ context-crossing-warn reads 0
└─ pre-compact-task-done-prompt reads 0
every context-pressure heuristic blind
secret-handler '' ──▶ if (!outputStr) return silentSuccess() :58
└─▶ redaction control scanned nothing
while its own suite stayed green
The estimator one is a measurement failure — numbers read 0 and look like calm. The secret-handler one is a control failure: a hook whose whole job is to catch credentials in tool output was handed an empty string and agreed there was nothing to redact.
token-estimator.test.ts tool_result · tool_result{content} · output · tool_output
secret-handler.test.ts tool_output × every single case
not one test supplied tool_response
→ the tests exercised a code path production never takes
This is the failure mode worth remembering: a green suite is only evidence about the inputs it actually sends. Both files tested the shape the code happened to read, so the tests and the bug agreed with each other.
Both sites now use the same ordering as mcp-output-transform:
preferred field first, aliases as fallbacks. Nothing that works today changes.
8 new cases run against origin/main's source: 8 FAIL same 8 against this branch: 8 PASS includes: estimateTokens(extract({tool_response: text})) > 0 — the regression as a number, not a shape
#3321 lists five findings. This closes one. The other four —
post-compact-recovery's additionalContext (PostCompact has no
such support), the StatusLine bridge file with zero writers,
pre-compact-saver reading token-budget-state.json while the
writer emits instruction-budget.json, and both PreCompact
blockers setting stopReason where CC reads reason —
are separate defects, and several depend on the autoCompactWindow
decision in #3325, which is still open.