Skip to content

Hook callback errors (Stream closed) on every tool call since v0.1.29 #554

Description

@sebyx07

Bug Description

Since upgrading to claude-agent-sdk v0.1.29+, every tool call produces noisy Error in hook callback hook_0: Stream closed errors in the output. The errors are non-fatal (tools complete successfully and execution continues), but they spam the console with ~50 lines of minified Claude CLI source code per tool call, making logs unreadable.

Environment

  • claude-agent-sdk: 0.1.31 (also reproduces on 0.1.29 and 0.1.30)
  • Python: 3.12
  • Platform: Ubuntu 24.04 (Linux 6.14.0)
  • Claude CLI bundled version: 2.1.33 (v0.1.31), 2.1.31 (v0.1.29)
  • Permission mode: bypassPermissions
  • Usage: Programmatic agent via `query()` with `ClaudeAgentOptions(allowed_tools=[...], permission_mode="bypassPermissions")`

Steps to Reproduce

  1. Install `claude-agent-sdk>=0.1.29`
  2. Run any agent query using `bypassPermissions` mode
  3. Observe every tool call (Read, Glob, Grep, Bash, etc.) produces the error

Error Output (per tool call)

[claude 10:58:16] Using tool: Read → CLAUDE.md
Error in hook callback hook_0: 6339 | Claude Code has been suspended...
...~50 lines of minified CLI source code...
6344 | `)}async sendRequest(H,$,A){...if(this.inputClosed)throw Error("Stream closed")...

error: Stream closed
      at sendRequest (/$bunfs/root/claude:6344:133)
      at sendRequest (/$bunfs/root/claude:6344:627)
      at <anonymous> (/$bunfs/root/claude:6344:1305)
      at callback (/$bunfs/root/claude:6344:1451)
      at _U1 (/$bunfs/root/claude:3151:1288)
      at _U1 (/$bunfs/root/claude:3151:1525)
      at <anonymous> (/$bunfs/root/claude:3144:1239)

[claude 10:58:16] Tool completed    ← tool still succeeds

This repeats for every single tool call in a session. A typical planning phase with 20+ tool calls produces hundreds of lines of error output.

Root Cause Analysis

v0.1.29 introduced new hook event types:

  • `SubagentStop`
  • `Notification`
  • `PermissionRequest`

The error appears to originate from the parent process's hook callback trying to handle permission requests via `sendRequest` over a stream that's closed or not applicable when `bypassPermissions` mode is active. The hook fires, fails to communicate over the stream, throws `Stream closed`, but execution continues because permissions are bypassed.

The minified source visible in the error includes the `SubagentStop` hook handling code:

hookEvent==="Stop"||hookEvent==="SubagentStop"

Workaround

Pinning to `claude-agent-sdk<0.1.29` (v0.1.28, CLI v2.1.30) eliminates the errors entirely.

Expected Behavior

Tool calls in `bypassPermissions` mode should not trigger hook callbacks that attempt stream communication, or the errors should be silently caught rather than dumped to stderr with minified source code.

Impact

  • Console output is unreadable during agent execution
  • Log files become bloated (each tool call adds ~50 lines of noise)
  • Makes it difficult to debug actual issues in agent output
  • Confusing for users who think the agent is crashing (it's not)

Activity

  1. sebyx07 commented on Feb 6, 2026

    @sebyx07
    Author

    My Environment

    • OS: Zorin OS 18 (Ubuntu 24.04 Noble / Linux 6.14.0-37-generic x86_64)
    • Python: 3.12.3
    • Node.js: v24.13.0
    • Claude Code CLI: 2.1.34 (system-installed)
    • claude-agent-sdk: Tested on 0.1.29, 0.1.30, and 0.1.31 — all reproduce. Pinned back to 0.1.28 as workaround.
    • Hardware: Desktop (MS-7C95 motherboard)

    The error occurs consistently on every tool call — Read, Glob, Grep, Bash, WebFetch, WebSearch — all produce the same Error in hook callback hook_0: ... Stream closed output. A single planning session with ~25 tool calls generates thousands of lines of noise in the logs.

    Confirmed: downgrading to claude-agent-sdk==0.1.28 (bundled CLI v2.1.30) completely eliminates the errors.

  2. sebyx07 commented on Feb 6, 2026

    @sebyx07
    Author

    Cleaned Logs

    Here is a cleaned excerpt from a real agent session running with claude-agent-sdk==0.1.31 and permission_mode="bypassPermissions". Every tool call follows the same pattern:

    Coding Style Analysis Phase (Opus model)

    Using model: opus (claude-opus-4-5-20251101) | Tools: Read, Glob, Grep, Bash, WebFetch, WebSearch
    
    [claude 10:58:16] Using tool: Read → CLAUDE.md
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          at sendRequest (/$bunfs/root/claude:6344:133)
          ...
    [claude 10:58:16] Tool completed                              ← succeeds
    
    [claude 10:58:16] Using tool: Glob → *.{eslintrc,prettierrc,editorconfig} in .
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          at sendRequest (/$bunfs/root/claude:6344:133)
          ...
    [claude 10:58:16] Tool completed                              ← succeeds
    
    [claude 10:58:16] Using tool: Glob → **/biome.json in .
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          ...
    [claude 10:58:16] Tool completed                              ← succeeds
    

    Planning Phase (same session, continues)

    [claude 10:58:59] Using tool: Glob → ./tmp/missing-stuff/**/* in .
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          ...
    [claude 10:58:59] Tool completed                              ← succeeds
    
    [claude 10:59:00] Using tool: Bash → ls -la ./tmp/missing-stuff/
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          ...
    [claude 10:59:00] Tool completed                              ← succeeds
    
    [claude 10:59:04] Using tool: Read → tmp/missing-stuff/0-priority-summary.md
    Error in hook callback hook_0: <~50 lines of minified CLI JS source>
    error: Stream closed
          ...
    [claude 10:59:04] Tool completed                              ← succeeds
    

    Key Observations

    1. Every tool type affected: Read, Glob, Grep, Bash — all produce the same error
    2. 100% reproduction rate: Not intermittent — happens on literally every tool call
    3. Non-fatal: Every tool completes successfully despite the error
    4. Minified source dump: The error includes lines 6339-6344 of the bundled Bun CLI source, including the SubagentStop hook handling code:
      hookEvent==="Stop"||hookEvent==="SubagentStop"
    5. Stack trace always identical: Always sendRequest → callback → _U1 at the same line numbers
    6. Session works fine on v0.1.28: Zero errors, clean output, identical functionality
  3. sebyx07 commented on Feb 6, 2026

    @sebyx07
    Author

    Raw Error Output

    This is actual unedited output from a session using query() with permission_mode="bypassPermissions". Every tool call produces this exact block. Here are two consecutive tool calls:

    [claude 10:58:16] Using tool: Read → CLAUDE.md
    Error in hook callback hook_0: 6339 | Claude Code has been suspended. Run `fg` to bring Claude Code back.
    6340 | Note: ctrl + z now suspends Claude Code, ctrl + _ undoes input.
    6341 | `)},FL=()=>{l1((SL)=>SL+1)};return uI?.on("suspend",J$),uI?.on("resume",FL),()=>{uI?.off("suspend",J$),uI?.off("resume",FL)}},[uI]);let Cf=lA.useMemo(()=>b5(eA).filter(at),[eA]),kf=lA.useMemo(()=>{if(!ZA)return null;let J$=aL.filter((G9)=>G9.type==="progress"&&G9.data.type==="hook_progress"&&(G9.data.hookEvent==="Stop"||G9.data.hookEvent==="SubagentStop"));if(J$.length===0)return null;let FL=[...new Set(J$.map((G9)=>G9.toolUseID))],SL=FL[FL.length-1];if(!SL)return null;if(aL.some((G9)=>G9.type==="system"&&G9.subtype==="stop_hook_summary"&&G9.toolUseID===SL))return null;let SD=J$.filter((G9)=>G9.toolUseID===SL),BD=SD.length,XI=aL.filter((G9)=>{if(G9.type!=="attachment")return!1;let W1=G9.attachment;return"hookEvent"in W1&&(W1.hookEvent==="Stop"||W1.hookEvent==="SubagentStop")&&"toolUseID"in W1&&W1.toolUseID===SL}).length,R1=SD.find((G9)=>G9.data.statusMessage)?.data.statusMessage;if(R1)return BD===1?`${R1}…`:`${R1}… ${XI}/${BD}`;let yB=SD[0]?.data.hookEvent==="SubagentStop"?"subagent stop":"stop";ret | ... truncated
    6342 | `);let A=await NVH(H,$);if(!A.success)throw Error(A.message);if(ND(`${mH.tick} ${A.message}
    6343 | `),!A.alreadyUpToDate)c("tengu_plugin_updated_cli",{plugin_id:H,old_version:A.oldVersion||"unknown",new_version:A.newVersion||"unknown"});await Y0(0)}catch(A){lPH(A,`update plugin "${H}"`)}}var QgA=K(()=>{SI();_$();z$();MU();OVH()})
    6344 | `)}async sendRequest(H,$,A){let L=HEB.randomUUID(),I={type:"control_request",request_id:L,request:H};if(this.inputClosed)throw Error("Stream closed");if(A?.aborted)throw Error("Request aborted");await this.write(I);let D=()=>{this.write({type:"control_cancel_request",request_id:L});let B=this.pendingRequests.get(L);if(B)B.reject(new oE)};if(A)A.addEventListener("abort",D,{once:!0})
    
    error: Stream closed
          at sendRequest (/$bunfs/root/claude:6344:133)
          at sendRequest (/$bunfs/root/claude:6344:627)
          at <anonymous> (/$bunfs/root/claude:6344:1305)
          at callback (/$bunfs/root/claude:6344:1451)
          at _U1 (/$bunfs/root/claude:3151:1288)
          at _U1 (/$bunfs/root/claude:3151:1525)
          at <anonymous> (/$bunfs/root/claude:3144:1239)
          at A (/$bunfs/root/claude:1240:1491)
          at MK$ (/$bunfs/root/claude:1240:1641)
          at iS (/$bunfs/root/claude:3144:5408)
    
    [claude 10:58:16] Tool completed
    
    [claude 10:58:16] Using tool: Glob → *.{eslintrc,prettierrc,editorconfig,eslintrc.js,eslintrc.json} in .
    Error in hook callback hook_0: 6339 | Claude Code has been suspended. Run `fg` to bring Claude Code back.
    6340 | Note: ctrl + z now suspends Claude Code, ctrl + _ undoes input.
    6341 | <same minified JS source repeated...>
    6344 | <same sendRequest / Stream closed pattern...>
    
    error: Stream closed
          at sendRequest (/$bunfs/root/claude:6344:133)
          at sendRequest (/$bunfs/root/claude:6344:627)
          <same stack trace...>
    
    [claude 10:58:16] Tool completed
    

    This repeats identically for every tool call in the session — Read, Glob, Grep, Bash, all affected. A typical agent session has 20-30 tool calls, producing thousands of lines of this error output.

  4. cdxiaodong commented on Feb 11, 2026

    @cdxiaodong

    so do i,

    src.executor_sdk.hooks.stop_hook - INFO - [Hook-Stop] transcript预览:
    {"type":"queue-operation","operation":"dequeue","timestamp":"2026-02-11T06:35:02.202Z","sessionId":"bd89ccbc-ce9e-4e4b-80c9-9143d114f822"}
    {"parentUuid":null,"isSidechain":false,"userType":"external","cwd":"","sessionId":"","version":"2.1.37","gitBranch":"main","type":"user","message":{"role":"user","content":"{\n  \"target\": \"/\",\n  \"task_type\": \"recon\",\n  \"task\": {\n    \"id\": \"...
    Error in hook callback hook_1: 6350 | Claude Code has been suspended. Run \`fg\` to bring Claude Code back.
    6351 | Note: ctrl + z now suspends Claude Code, ctrl + _ undoes input.
    6352 | `)},JI=()=>{r9((SI)=>SI+1)};return mE?.on("suspend",JA),mE?.on("resume",JI),()=>{mE?.off("suspend",JA),mE?.off("resume",JI)}},[mE]);let ZD=lL.useMemo(()=>m_(eL).filter(Ds),[eL]),cD=lL.useMemo(()=>{if(!ZL)return null;let JA=aI.filter((Bf)=>Bf.type==="progress"&&Bf.data.type==="hook_progress"&&(Bf.data.hookEvent==="Stop"||Bf.data.hookEvent==="SubagentStop"));if(JA.length===0)return null;let JI=[...new Set(JA.map((Bf)=>Bf.toolUseID))],SI=JI[JI.length-1];if(!SI)return null;if(aI.some((Bf)=>Bf.type==="system"&&Bf.subtype==="stop_hook_summary"&&Bf.toolUseID===SI))return null;let R1=JA.filter((Bf)=>Bf.toolUseID===SI),f1=R1.length,XE=aI.filter((Bf)=>{if(Bf.type!=="attachment")return!1;let K9=Bf.attachment;return"hookEvent"in K9&&(K9.hookEvent==="Stop"||K9.hookEvent==="SubagentStop")&&"toolUseID"in K9&&K9.toolUseID===SI}).length,y9=R1.find((Bf)=>Bf.data.statusMessage)?.data.statusMessage;if(y9)return f1===1?`${y9}\u2026`:`${y9}\u2026 ${XE}/${f1}`;let h$=R1[0]?.data.hookEvent==="SubagentStop"?"subagent stop":"stop";ret | ... truncated 
    6353 | `);let L=await lWH(H,A);if(!L.success)throw Error(L.message);if(z1(`${cH.tick} ${L.message}
    6354 | `),!L.alreadyUpToDate)d("tengu_plugin_updated_cli",{plugin_id:H,old_version:L.oldVersion||"unknown",new_version:L.newVersion||"unknown"});await PM(0)}catch(L){BQH(L,`update plugin "${H}"`)}}var imL=C(()=>{SE();KA();YA();_4();iWH()});function rzA(){return nmL.default.createElement(F,null,"MCP servers may execute code or access system resources. All tool calls require approval. Learn more in the"," ",nmL.default.createElement(pE,{url:"https://code.claude.com/docs/en/mcp"},"MCP documentation"),".")}var nmL;var rmL=C(()=>{uH();uH();nmL=j(s(),1)});function DU$({serverNames:H,onDone:A}){function L(E){let $=FI()||{},M=$.enabledMcpjsonServers||[],f=$.disabledMcpjsonServers||[],[D,U]=dnL(H,(G)=>E.includes(G));if(d("tengu_mcp_multidialog_choice",{approved:D.length,rejected:U.length}),D.length>0){let G=[...new Set([...M,...D])];EE("localSettings",{enabledMcpjsonServers:G})}if(U.length>0){let G=[...new Set([...f,...U])];EE("localSettings",{disabledMcpjsonServers:G})}A()}let I=fU$.useCallback(()=>{let $=(FI()||{}).disable | ... truncated 
    6355 | `)}async sendRequest(H,A,L){let I=aU$.randomUUID(),E={type:"control_request",request_id:I,request:H};if(this.inputClosed)throw Error("Stream closed");if(L?.aborted)throw Error("Request aborted");await this.write(E);let $=()=>{this.write({type:"control_cancel_request",request_id:I});let M=this.pendingRequests.get(I);if(M)M.reject(new H8)};if(L)L.addEventListener("abort",$,{once:!0});try{return await new Promise((M,f)=>{this.pendingRequests.set(I,{request:{type:"control_request",request_id:I,request:H},resolve:(D)=>{M(D)},reject:f,schema:A})})}finally{if(L)L.removeEventListener("abort",$);this.pendingRequests.delete(I)}}createCanUseTool(H){return async(A,L,I,E,$)=>{let M=await Z2(A,L,I,E,$);if(M.behavior==="allow"||M.behavior==="deny")return M;let f=await au9(A.name,$,L,I,M.suggestions);if(f)return f;try{H?.();let D=await this.sendRequest({subtype:"can_use_tool",tool_name:A.name,input:L,permission_suggestions:M.suggestions,blocked_path:M.blockedPath,decision_reason:ru9(M.decisionReason),tool_use_id:$,agent_id:I | ... truncated 
    
    AbortError: 
          at $ (/$bunfs/root/claude:6355:332)
          at I (/$bunfs/root/claude:1445:15515)
    
  5. added a commit that references this issue on Feb 15, 2026
    e74f107
  6. qing-ant commented on Mar 25, 2026

    @qing-ant
    Contributor

    This issue has the same root cause as #730 — wait_for_result_and_end_input() applies a 60-second timeout that closes stdin prematurely, killing hook callbacks. The "Stream closed" errors on every tool call stem from this timeout shutting down the bidirectional control protocol.

    PR #731 removes the timeout entirely when hooks or MCP servers are active. Closing as duplicate of #730.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions