[BUG] claude -p with tool use hangs non-deterministically when spawned from Bash tool
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Running claude -p from within Claude Code's Bash tool hangs non-deterministically when the nested session requires tool use (Bash, Read, or any other tool). Simple prompts that don't require tool use always succeed.
Root Cause Investigation
We investigated this systematically and traced it to file descriptor inheritance issues between the parent Claude Code session and the nested claude -p process. The Bash tool sets up stdin/stdout/stderr via unix sockets and pipes (visible in lsof output), and the nested claude binary inherits these fds. When the nested session attempts tool execution, it interferes with the parent's fd setup, causing a deadlock.
Key findings from our investigation:
- Non-tool prompts always work:
claude -p "respond with X"completes instantly from within Bash tool - Tool-use prompts hang non-deterministically: Same invocation sometimes works, sometimes hangs indefinitely
- Not a permissions issue: Hangs even with
--dangerously-skip-permissions - Not a sandbox issue: Hangs with
dangerouslyDisableSandbox: trueon the outer Bash call - Not project-level contention: Hangs even when
cd /tmpbefore invoking - Not model-specific: Hangs with both Opus and Sonnet
- All tools affected: Bash, Read, Glob — any tool use triggers the hang
- Process inspection: Hung processes show unix sockets and pipes in
lsofbut never create session JSONL files, suggesting they stall during initialization before the session starts
Session JSONL Evidence
When a nested claude -p does succeed with tool use and run_in_background, the session JSONL shows the headless session waits for background tasks to complete before returning — it injects a task-notification as a new user turn after end_turn, processes it, then exits. This is correct behavior. But the hanging sessions never get far enough to write any JSONL at all.
Related Issues
This is related to (but distinct from) several closed issues:
- #28407 — Bash tool output suppressed when child process spawns
claude -p(fd 3 interference) - #9026 — Claude CLI hangs without TTY despite
-pflag - #16306 — Claude Code hangs when
/dev/stdinis read in sandbox - #7497 — Process hangs when reading InputStream from headless execution
Those issues were closed as duplicates or stale, but the underlying fd inheritance problem persists.
Workaround: Use the Python Agent SDK
The Python Agent SDK (claude-agent-sdk) avoids this entirely because it communicates with the API directly rather than spawning a subprocess. It provides the same tools (Bash, Read, Edit, Glob, Grep, Agent) and writes sessions to the same ~/.claude/projects/ JSONL store, but without the fd inheritance issues:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
async for msg in query(
prompt="Use Bash to run 'echo HI'. Then say DONE.",
options=ClaudeAgentOptions(
allowed_tools=["Bash"],
cwd="/path/to/project",
),
):
if isinstance(msg, ResultMessage):
print(msg.result)
asyncio.run(main())
For parallel orchestration (the primary use case for nested headless sessions):
async def run_task(prompt: str) -> str:
async for msg in query(
prompt=prompt,
options=ClaudeAgentOptions(allowed_tools=["Bash", "Read", "Edit"]),
):
if isinstance(msg, ResultMessage):
return msg.result
results = await asyncio.gather(
run_task("Review auth module"),
run_task("Review database module"),
)
This is reliable because there's no subprocess fd inheritance — the SDK is a direct API client.
What Should Happen?
claude -p with tool use should work reliably when invoked from within Claude Code's Bash tool, just as non-tool prompts do.
Steps to Reproduce
From within an interactive Claude Code session, run via the Bash tool:
# This works (no tool use):
claude -p "Say hello" --output-format text --dangerously-skip-permissions
# This hangs non-deterministically (requires tool use):
claude -p "Use the Bash tool to run 'echo HI'. Say DONE." --output-format text --dangerously-skip-permissions
Run the tool-use version 5-10 times — some will succeed, some will hang indefinitely.
Environment
- OS: macOS 15.7.2 (Darwin 24.6.0, arm64)
- Claude Code Version: 2.1.76
- Shell: zsh
Claude Model
Claude Opus 4.6 (also reproduces with Sonnet)
Is this a regression?
Unknown — nested claude -p with tool use may never have worked reliably from within Bash tool.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗