PostToolUse callback hooks: return value (updatedMCPToolOutput) silently discarded, never persisted to transcript

Resolved 💬 2 comments Opened Apr 14, 2026 by fwdawid Closed Apr 14, 2026

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:

  1. The live API call does not see the replacement (no updatedMCPToolOutput propagated)
  2. 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

  1. Register a PostToolUse callback hook that returns { hookSpecificOutput: { hookEventName: 'PostToolUse', updatedMCPToolOutput: [...] } }
  2. Trigger an MCP tool call
  3. 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 (ykKT37) 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

View original on GitHub ↗

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