Headless `claude -p` intermittently starts with no user prompt (system context only), then exits 0

Status Open
Reported on v2.1.215
Maintainer reply None cached
Activity 0 comments · opened Aug 8, 2026

Summary

In headless print mode (claude -p), the process intermittently starts, loads its system context, and then behaves as if no user prompt was ever supplied. The model answers with some variant of "I don't see a request in your message, just system context. What would you like help with?", and the process exits 0 after about six seconds.

Exit 0 with no work done is the damaging part: any scheduler treats it as success. In my case it silently skipped scheduled jobs for days before I added a sentinel check to catch it.

The prompt is passed as a real argv element after --, and the prompt file is present and unchanged on every occurrence. An immediate retry with byte-identical arguments almost always succeeds, so it is nondeterministic rather than a quoting or parsing bug.

8 occurrences in 10 days, across two different scheduled jobs, at unrelated times of day.

Environment

  • Claude Code 2.1.215
  • macOS 26.6, arm64
  • Invoked from a bash script under launchd (non-interactive, stdin is /dev/null, no TTY)
  • Auth: OAuth (subscription), dedicated CLAUDE_CONFIG_DIR

Invocation

"$CLAUDE_BIN" -p \
  --model "$MODEL" \
  --append-system-prompt-file "$GUARD" \
  --permission-mode acceptEdits \
  --allowedTools "${TOOLS[@]}" \
  "${ADD[@]}" \
  -- "$(cat "$PROMPT_FILE")" \
  > "$log" 2>&1 &
  • $PROMPT_FILE is 17,592 bytes for one job and 8,687 bytes for the other. Both begin with YAML frontmatter, which is why the prompt goes after --.
  • $GUARD is a ~3 KB system prompt file.
  • ${TOOLS[@]} is a handful of tool names; ${ADD[@]} is one or two --add-dir flags.

Evidence

Each line is one failed run. The text is the model's entire stdout, the complete log file, 37 to 111 bytes.

| When (local) | Job | Model's entire output |
|---|---|---|
| 2026-07-30 10:45:14 | ingest | I don't see a request in your message yet, just system context. What would you like help with? |
| 2026-08-02 06:04:56 | nightly | I don't see an actual request in your message, just system context. What would you like help with? |
| 2026-08-03 18:29:02 | ingest | I'm set up and ready. What would you like to work on? |
| 2026-08-05 19:47:13 | ingest | I don't see a specific request in your message yet, just the loaded context. What would you like help with? |
| 2026-08-06 07:07:07 | ingest | Ready, what would you like to do? |
| 2026-08-06 11:22:37 | ingest | No task or question came through in your message, just the system context load. What would you like help with? |
| 2026-08-06 18:36:53 | ingest | No task specified yet. What would you like help with? |
| 2026-08-08 03:00:05 | nightly | I don't see an actual request in your message, just system context. What would you like help with? |

(One character normalized: the 2026-08-06 07:07 reply used an em dash after "Ready".)

Worth noting the wording differs every time, and one reply refers to the job spec arriving as a system prompt while no user request did:

The inbox-ingest job specification just loaded via a system prompt, but I haven't been given an actual user request or task to execute yet.

That suggests the system prompt (--append-system-prompt-file) arrives intact while the positional user prompt does not.

Ruled out

  • Not the prompt file. Present and byte-identical on every occurrence (stat confirms no modification between failures; one file has been untouched since 2026-08-03).
  • Not shell quoting or the arg parser. The retry uses byte-identical argv and succeeds. This same symptom originally appeared when the prompt was fed via stdin redirect; I moved it to argv after -- on 2026-07-30 specifically to fix it, and it kept happening.
  • Not ARG_MAX. 17.5 KB is far under the macOS limit, and an oversized argv would fail at exec with E2BIG rather than starting successfully.
  • Not an auto-update race. The newest installed version predates all eight failures by weeks.
  • Not a cold start. On 2026-08-06 three separate runs failed at 07:07, 11:22 and 18:36, with healthy runs in between; the 18:36 run then failed three consecutive attempts about a minute apart.
  • Not concurrency within the account. Every invocation is serialized behind a lock, one at a time.

Impact

exit 0 makes this invisible to a scheduler. Two of the eight cost real work:

  • 2026-08-06 18:36: three consecutive attempts all came back empty, so the job gave up entirely. Queued input was not processed until the next day.
  • 2026-08-08 03:00: attempt 1 empty; only the retry did the night's work.

Before I added a sentinel, the failure mode was completely silent: the wrapper logged success, downstream health checks reported ok, and the work simply did not happen.

Suggested fix

Ideally, if no user prompt is present, claude -p should exit non-zero with a clear diagnostic rather than starting a turn with an empty user message. That alone would make this detectable instead of silent, even if the underlying race is harder to pin down.

Workaround, for anyone else hitting this

Require the prompt to print an explicit end-of-run sentinel, and treat exit 0 without that sentinel as failure. Detect the empty-prompt shape specifically (tiny log plus "what would you like" phrasing) and retry it, since nothing ran and nothing was written, so a retry cannot duplicate side effects. Distinguish it from a run that died partway, which must not be blindly retried.

View original on GitHub ↗