[BUG] `claude --print -p` silently exits 143 after ~14 tool round-trips

Resolved 💬 3 comments Opened Apr 25, 2026 by glenngillen Closed Apr 28, 2026

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:

  1. Aborts the in-flight SSE stream by hanging up on api.anthropic.com
  2. Runs registered atexit handlers (MCP SIGINT, LSP shutdown,

shell-snapshot cleanup)

  1. 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_

View original on GitHub ↗

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