PostToolUse callback hooks: return value (updatedMCPToolOutput) silently discarded, never persisted to transcript
Bug
When all PostToolUse hooks are JavaScript callbacks (registered via HookCallback), the SDK's Xh function takes a "callback-only fast path" that ignores the return value entirely. This means updatedMCPToolOutput from the hook response is never yielded, so:
- The live API call does not see the replacement (no
updatedMCPToolOutputpropagated) - The session transcript JSONL keeps the original tool response content
The feature works correctly when at least one non-callback hook (shell command, prompt, agent, or function hook) is registered for the same event — then the full processing path is taken where FoY captures callback return values via c$7.
Reproduction
- Register a
PostToolUsecallback hook that returns{ hookSpecificOutput: { hookEventName: 'PostToolUse', updatedMCPToolOutput: [...] } } - Trigger an MCP tool call
- Observe that the transcript JSONL file contains the original tool response, not the replacement from
updatedMCPToolOutput
Root Cause
In Xh (the hook executor), there are two paths:
let Z = D.filter((h) => !WsK(h)); // Z = non-callback hooks
if (Z.length > 0) {
// Full path — processes callback return values via FoY → c$7
// c$7 extracts hookSpecificOutput.updatedMCPToolOutput
// This path DOES yield updatedMCPToolOutput ✅
} else {
// Callback-only fast path
for (let [g, { hook: c }] of D.entries())
if (c.type === "callback")
await c.callback(q, _, Y, g, p); // return value discarded!
return; // returns without yielding anything ❌
}
The callback-only fast path calls the hook but discards its return value, then returns without yielding. The upstream generator (ykK → T37) never sees updatedMCPToolOutput, so q6 stays as the original n.data.
Expected Behavior
The callback-only fast path should process callback return values the same way FoY does in the mixed-hooks path — extract hookSpecificOutput fields and yield them.
Workaround
We TOON-encode external MCP tool results at transcript persist time (after reading the JSONL from disk, before writing to PostgreSQL) as a workaround.
Environment
@anthropic-ai/claude-agent-sdk(latest as of 2026-04-14)- Using the SDK programmatically (not CLI) with in-process callback hooks
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗