[BUG] `claude --print -p` silently exits 143 after ~14 tool round-trips
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?
Claude Code 2.1.119, --print -p "<prompt>" invocation: after ~14 tool-use round-trips, the CLI disconnects from the in-flight /v1/messages SSE response (HTTP 200, body-not-yet-streamed), runs atexit handlers, and calls exit(143) — emitting no output to stdout/stderr. mitmproxy capture confirms the API server responded with valid headers; the disconnect was client-initiated. Identical prompt fed via stdin (claude --print < prompt.txt) completes successfully. Reproducible on macOS 26.2, Apple Silicon, with prompts that trigger ≥14 tool calls.
Environment
- macOS: 26.2 arm64
- Claude Code: 2.1.119 (Claude Code)
- Claude binary: /opt/homebrew/bin/claude: Mach-O 64-bit executable arm64
Hypothesis
Claude --print mode appears to enforce a turn-count or
context-size guard that fires after ~14 tool-use round-trips.
When the guard fires, the implementation:
- Aborts the in-flight SSE stream by hanging up on api.anthropic.com
- Runs registered atexit handlers (MCP SIGINT, LSP shutdown,
shell-snapshot cleanup)
- Calls exit(143)
The exit code 143 (= 128 + 15) appears to be a deliberate
shell-convention signal-to-self indicator; no actual signal is
involved. The user-facing symptom is an empty stdout, empty stderr,
non-zero exit, with no diagnostic information.
Suggested fixes from a user perspective:
- Print a diagnostic to stderr when this guard fires (e.g.
'aborted: max tool-use iterations reached for --print mode')
- Use exit code that distinguishes this from external SIGTERM
(e.g. exit 70 / EX_SOFTWARE)
- Document the cap in --print --help
- Or: lift the cap, since stdin invocation has no equivalent issue
What Should Happen?
Claude should keep running
Error Messages/Logs
### 1a. BSC_exit syscall (ktrace)
Captured via 'ktrace trace -b 1024 --ndjson' on macOS:
args[0] = 143 is the value passed to exit(2). The kernel's proc_exit
W-status confirms (0x8F00 = WIFEXITED true, WEXITSTATUS=143, no signal):
### 1b. CoreAnalytics atexit handler ran (unified log)
Apple's CoreAnalytics framework registers atexit() handlers; they
fire only on voluntary exit() (not on signal-kill):
2026-04-25 13:45:49.070740+1000
2026-04-25 13:45:49.070853+1000
2026-04-25 13:45:49.629451+1000
### 1c. claude's own --debug-file ends mid-stream
Last lines of `--debug all --debug-file ...` before exit. Note that
the very last log line is 'received first chunk' of an API request,
followed only by clean MCP/LSP teardown — no error, no warn, no
stall trigger:
2026-04-25T03:45:42.244Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=[ID_REDACTED] source=sdk
2026-04-25T03:45:44.690Z [DEBUG] Stream started - received first chunk
2026-04-25T03:45:44.700Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=[ID_REDACTED] source=sdk
2026-04-25T03:45:47.438Z [DEBUG] Stream started - received first chunk
2026-04-25T03:45:49.071Z [DEBUG] MCP server "pencil": Sending SIGINT to MCP server process
2026-04-25T03:45:49.072Z [DEBUG] LSP server manager shut down successfully
2026-04-25T03:45:49.073Z [DEBUG] Cleaned up session snapshot: /private/var/lib/claude/.claude/shell-snapshots/snapshot-zsh-1777088726273-dx1xg5.sh
2026-04-25T03:45:49.073Z [DEBUG] MCP server "pencil": STDIO connection closed after 33s (cleanly)
2026-04-25T03:45:49.073Z [DEBUG] MCP server "pencil": Cleared connection cache for reconnection
2026-04-25T03:45:49.124Z [DEBUG] MCP server "pencil": MCP server process exited cleanly
## Evidence 2: client-initiated disconnect on 14th /v1/messages
Captured via mitmproxy with HTTPS_PROXY injected post-sudo and
mitmproxy CA installed in system keychain.
Summary of all /v1/messages flows in one --print invocation:
FLOW #1: POST https://api.anthropic.com/v1/messages?beta=true
request size: 303949 bytes
response code: 200
response size: 11247 bytes
FLOW #2: POST https://api.anthropic.com/v1/messages?beta=true
request size: 309097 bytes
response code: 200
response size: 4458 bytes
FLOW #3: POST https://api.anthropic.com/v1/messages?beta=true
request size: 309600 bytes
response code: 200
response size: 5223 bytes
FLOW #4: POST https://api.anthropic.com/v1/messages?beta=true
request size: 312553 bytes
response code: 200
response size: 4329 bytes
FLOW #5: POST https://api.anthropic.com/v1/messages?beta=true
request size: 317364 bytes
response code: 200
response size: 8050 bytes
FLOW #6: POST https://api.anthropic.com/v1/messages?beta=true
request size: 321382 bytes
response code: 200
response size: 4131 bytes
FLOW #7: POST https://api.anthropic.com/v1/messages?beta=true
request size: 339991 bytes
response code: 200
response size: 10382 bytes
FLOW #8: POST https://api.anthropic.com/v1/messages?beta=true
request size: 355192 bytes
response code: 200
response size: 3562 bytes
FLOW #9: POST https://api.anthropic.com/v1/messages?beta=true
request size: 366285 bytes
response code: 200
response size: 30736 bytes
FLOW #10: POST https://api.anthropic.com/v1/messages?beta=true
request size: 368505 bytes
response code: 200
response size: 5534 bytes
FLOW #11: POST https://api.anthropic.com/v1/messages?beta=true
request size: 369045 bytes
response code: 200
response size: 3995 bytes
FLOW #12: POST https://api.anthropic.com/v1/messages?beta=true
request size: 378110 bytes
response code: 200
response size: 4725 bytes
FLOW #13: POST https://api.anthropic.com/v1/messages?beta=true
request size: 387346 bytes
response code: 200
response size: 4389 bytes
FLOW #14: POST https://api.anthropic.com/v1/messages?beta=true
request size: 389828 bytes
response code: 200
response size: 0 bytes
Notable points:
- All 14 requests received HTTP 200 + content-type: text/event-stream
- Flows 1-13: response body is a complete SSE stream ending with
'event: message_stop'
- Flow 14: response body is 0 bytes. mitmproxy logs the disconnect
source: '<< 200 OK (content missing) << Client disconnected.'
- Request body grows monotonically (304 KB -> 390 KB) as tool
results are appended; this is normal conversation growth.
Mitmproxy log line proving the disconnect was client-initiated:
<< 200 OK (content missing)
<< Client disconnected.
--
<< 200 OK (content missing)
<< Client disconnected.
--
[IP]:55371: POST https://http-intake.logs.us5.datadoghq.com/api/v2/logs
<< Client disconnected.
## Evidence 3: stdin invocation does not hit this path
Same prompt content, only difference is delivery channel:
# Failing:
claude --print -p "$(cat prompt.txt)" < /dev/null # exit 143
# Working:
claude --print < prompt.txt # exit 0
# (in test repro: 247s elapsed, did real work, committed)
Steps to Reproduce
claude --dangerously-skip-permissions --no-session-persistence \
--model opus --print -p "$(cat large-prompt.txt)" < /dev/null
# elapsed: variable (7-130s observed across runs)
# exit code: 143
# stdout: empty
# stderr: empty
Where 'large-prompt.txt' is a ~210 KB prompt that asks claude to perform
code-exploration tool calls (Read, Grep, Bash) on a real codebase. The
prompt structure is irrelevant; what matters is that it elicits >=14
tool-use turns in a single --print invocation.
Identical prompt fed via stdin (claude --print < large-prompt.txt)
completes successfully (exit 0).
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.119
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗