claude-code - 💡(How to fix) Fix Bash tool wedges into 'Stream closed' state after long-running subprocess

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The Bash tool in Claude Code intermittently wedges into a state where Python invocations and Write/Edit tool operations fail with Stream closed, while simple Bash commands (echo, git status, date) keep working. The wedge persists for the rest of the session — only "Restart Claude Code" recovers, and even that doesn't fully kill the old claude CLI process.

This has happened to us at least three times in the past two weeks. Each occurrence has correlated with a long-running subprocess being run via the Bash tool earlier in the session (a uv install taking 2m21s, a 5+ min Mem0 re-seed script, a Claude-Agent-SDK-powered Python script that itself spawns an inner claude CLI subprocess).

Root Cause

Hypothesis (offered for discussion, not asserted as root cause)

Fix Action

Fix / Workaround

Workarounds we've adopted

Code Example

$ pgrep -fl 'claude --output-format'
45035  /Users/weaver1/.vscode/extensions/anthropic.claude-code-2.1.145-darwin-arm64/.../claude --output-format stream-json ...   # last night, etime ~10h
83103  claude --output-format stream-json ...   # current session, etime ~2m
RAW_BUFFERClick to expand / collapse

Summary

The Bash tool in Claude Code intermittently wedges into a state where Python invocations and Write/Edit tool operations fail with Stream closed, while simple Bash commands (echo, git status, date) keep working. The wedge persists for the rest of the session — only "Restart Claude Code" recovers, and even that doesn't fully kill the old claude CLI process.

This has happened to us at least three times in the past two weeks. Each occurrence has correlated with a long-running subprocess being run via the Bash tool earlier in the session (a uv install taking 2m21s, a 5+ min Mem0 re-seed script, a Claude-Agent-SDK-powered Python script that itself spawns an inner claude CLI subprocess).

Environment

  • macOS 26.4.1 (Apple Silicon M5, 24 GB)
  • Claude Code: VSCode extension anthropic.claude-code-2.1.145-darwin-arm64
  • CLI: --output-format stream-json --verbose --input-format stream-json --max-thinking-tokens 31999 --permission-prompt-tool stdio --setting-sources=user,project,local --permission-mode auto --include-partial-messages --debug --debug-to-stderr --enable-auth-status --no-chrome --replay-user-messages
  • Shell: zsh 5.9
  • Python via uv (Astral)
  • claude-agent-sdk 0.2.83 (in the most recent occurrence)

Symptom

After the wedge:

  • ✅ Works: echo "ping", date, git status, pytest, tail, file reads, simple shell commands.
  • ❌ Fails with Stream closed: .venv/bin/python -c "...", python script.py, the Write tool, MCP write tools, cat > file << EOF, touch /tmp/test.

dangerouslyDisableSandbox: true does NOT bypass the wedge — it surfaces above the sandbox layer.

Reproduction (best-effort, not isolated)

Each occurrence has involved at least one of:

  1. A long-running subprocess via the Bash tool (>2 min, brushing the default timeout).
  2. A Bash(run_in_background=true) task that runs for several minutes.
  3. A Python script that uses claude-agent-sdk's SubprocessCLITransport to spawn an inner claude CLI subprocess.

We have not been able to isolate a minimal repro that doesn't burn the working session, so we haven't tried to bisect the trigger more precisely.

Evidence from the most recent occurrence (2026-05-20)

Tool result IPC lives at /tmp/claude-501/<cwd-slug>/<session-uuid>/tasks/<id>.output. From the session in question:

TimeTaskSizeNotes
22:54:??b1eyzprpr.output168 Buv add claude-agent-sdkPrepared 1 package in 2m 21s
22:57:??bl3qyrg2t.output0 BFirst wedge signal — Bash tool result empty

The 0-byte file is the first observable IPC failure. The preceding uv install ran for 2m 21s, right at the Bash tool's default 2-min timeout boundary.

Process state after restart

After "Restart Claude Code", the previous session's claude CLI process is still alive — same parent (VSCode extension host), 37 open unix-domain sockets, ~10h elapsed at the time of writing. It is not actively communicating with the extension host (the new claude PID has taken over that channel) but it is also not being killed. Its MCP server child processes (claude-mem, mem0-poc) are also still alive.

This is mostly cosmetic — they idle quietly — but they sit on FDs / unix sockets and accumulate across restarts.

$ pgrep -fl 'claude --output-format'
45035  /Users/weaver1/.vscode/extensions/anthropic.claude-code-2.1.145-darwin-arm64/.../claude --output-format stream-json ...   # last night, etime ~10h
83103  claude --output-format stream-json ...   # current session, etime ~2m

Hypothesis (offered for discussion, not asserted as root cause)

The unix-socket IPC channel between the per-session claude CLI and the VSCode extension host degrades when a single Bash tool invocation runs a long-lived subprocess chain that stresses the IPC pipe (heavy buffered output, FD inheritance fanout from grandchildren, or timeout-triggered uncoordinated kill of a child while a grandchild survives). Once degraded, large-payload tool operations (Python stdout, Write tool file content) fail with "Stream closed" while small synchronous Bash commands continue working — possibly via a different fast-path.

The claude-agent-sdk interaction is relevant because its SubprocessCLITransport inherits the parent process's stderr FD by default (no stderr callback registered ⇒ stderr=None ⇒ inherited). In Claude-in-Claude mode this chains: outer Claude Code → Bash tool shell → Python → SDK → inner claude CLI, all sharing one stderr FD path. The SDK's atexit cleanup hook (_kill_active_children) does not fire on SIGKILL, so a timed-out Bash tool can orphan the inner CLI with its inherited FDs.

Questions / asks for maintainers

  1. Is the unix-socket IPC channel between the CLI and the VSCode extension host known to be sensitive to long-lived subprocess fanout? Is there a buffer or FD-count threshold that would explain the selective failure (Python/Write fail, simple Bash works)?
  2. Is there a recommended pattern for invoking long-running subprocesses from the Bash tool? (We're now defaulting to nohup setsid … &; disown + a sentinel file for unavoidable long ops, but documentation here would help.)
  3. Should "Restart Claude Code" terminate the prior session's claude CLI process? If so, the current behavior on macOS VSCode is leaving it resident. If not, is there a documented way to reap it cleanly?
  4. Is the claude-agent-sdk Claude-in-Claude pattern (using SubprocessCLITransport from inside a Bash-tool-spawned Python process) supported / tested? If yes, are there guidance docs on stderr handling, FD hygiene, or cleanup that we should be following?

Workarounds we've adopted

  • Don't run subprocesses expected to take >60s via the Bash tool. Use a separate Terminal window, a launchd job, or nohup setsid <cmd> >/tmp/out 2>&1 &; disown.
  • Set timeout explicitly on the Bash tool for unavoidable long ops (max 600000 ms).
  • After restart, manually kill the prior session's claude CLI + descendants.

Happy to gather more data if there's specific instrumentation that would help. We have several /tmp/claude-501/.../tasks/*.output artifacts and ps/lsof snapshots from the wedge if useful.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING