Status: ROOT CAUSE IDENTIFIED First observed: 2026-01-13 Last updated: 2026-01-13
Happy CLI crashes with exit code 1 when resuming sessions in stream-json mode. The crash is caused by Claude Code's "only prompt commands are supported in streaming mode" error.
Root Cause: GitHub issue #16712 - When resuming with --input-format stream-json, Claude Code injects a synthetic "No response requested" message BEFORE reading stdin, breaking the message chain.
"only prompt commands are supported in streaming mode"
This error appears 27 times in a single session log, confirming it's the primary issue.
- Happy starts session, Claude responds, session ends with pending state
- Happy resumes with
--resume <session-id> --input-format stream-json - Claude Code injects:
[assistant] "No response requested."(synthetic) - Happy sends user message via stdin
- Claude Code rejects: "only prompt commands are supported in streaming mode"
- Claude Code exits with code 1
Claude Code's stream-json resume behavior expects ONLY prompt commands (new user prompts). When Happy sends a continuation message after the synthetic injection, Claude Code rejects it.
This is a known Claude Code limitation, not a Happy bug.
[11:12:12.835] [claudeRemote] Message result
[11:12:12.836] [claudeRemote] Thinking state changed to: false
[11:12:12.837] [claudeRemote] Result received, exiting claudeRemote
[11:12:12.838] [PUSH] sendToAllDevices called
[11:12:12.839] [MessageQueue2] Waiting for messages...
[11:12:13.232] [Claude SDK] Process exit: code=1, stderr=43 bytes, stdout=5000 bytes
Key observation: ~395ms between "Waiting for messages" and crash.
Only contains version banner (43 bytes):
[90mUsing Claude Code v2.1.3 from npm[0m
No actual error message - Claude Code exits without explaining why.
Created scripts/reproduce-crash.mjs with identical flags:
--input-format stream-json--output-format stream-json--permission-prompt-tool stdio--mcp-config(with Happy MCP server)--resume(with valid session ID)DEBUG=trueenvironment
Result: Process stayed alive 30+ seconds. No crash.
Checked streamToStdin logs - no stdin.end() called before crash.
stdin pipe was still open and waiting.
GitHub issue #11701 describes MCP transport timeout closing stdin. Our crash pattern differs - stdin stays open.
GitHub issue #16712 describes resume injecting "No response requested". UPDATE: This IS the cause. The error "only prompt commands are supported in streaming mode" appears 27 times in logs.
Note: The race condition hypothesis is now superseded by the confirmed root cause above. Keeping for historical reference.
When Happy waits for next user message, multiple systems are active:
- Push notifications -
sendToAllDevicessends to mobile - Socket updates - Broadcasts ready state to clients
- Mode hash checks - Monitors for mode changes
- Keep-alive messages - Periodic socket heartbeats
- Mode hash change detection triggers process restart while stdin write in progress
- Socket update causes state change that affects Claude Code process
- Push notification callback interferes with message queue
- Multiple concurrent awaits on shared state
Options (AskUserQuestion or <options> XML) create longer idle time while user thinks.
More idle time = more opportunity for race condition to trigger.
// Lines 190-213: After result, waits for next message
if (message.type === 'result') {
updateThinking(false);
logger.debug('[claudeRemote] Result received, exiting claudeRemote');
opts.onReady(); // <-- Triggers push notifications, socket updates
const next = await opts.nextMessage(); // <-- Blocks here during crash
// ...
}// Lines 349-353: Mode hash check
if ((modeHash && msg.hash !== modeHash) || msg.isolate) {
logger.debug('[remote]: mode has changed, pending message');
pending = msg;
return null; // <-- Could this race with other operations?
}// Lines 371-376: stderr capture (DEBUG enabled)
child.stderr.on('data', (data) => {
const text = data.toString()
stderrBuffer += text
if (process.env.DEBUG) {
console.error('Claude Code stderr:', text)
}
})In claudeRemote.ts, add logging:
// Before nextMessage()
logger.debug('[claudeRemote] About to wait for next message');
// In nextMessage callback
logger.debug('[claudeRemote] nextMessage resolved/rejected');Log timestamps for:
- Push notification start/complete
- Socket update start/complete
- Mode hash check timing
- Any async operation during idle
spawnHappyCLI.ts already modified to set DEBUG=true.
Next crash will capture full stderr.
Review all operations that modify:
modevariable in claudeRemotemodeHashin claudeRemoteLaunchermessagesPushableAsyncIterable state- Any global/shared state
Location: scripts/reproduce-crash.mjs
# Basic test (no resume)
node scripts/reproduce-crash.mjs
# With resume (needs valid session)
cd /Users/craigvanheerden/Repos
node infrastructure/happy-cli/scripts/reproduce-crash.mjs <session-id>Configuration flags at top of file:
USE_MCP- Toggle MCP configMCP_URL- MCP server URLUSE_RESUME- Toggle resume flagSESSION_ID- From command line arg
| Issue | Title | Relevance |
|---|---|---|
| #11701 | VS Code extension exit code 1 | MCP transport timeout pattern |
| #16712 | tool_result via stdin on resume | Synthetic message injection |
| #3187 | stream-json input hang | Second message hang pattern |
| #5034 | Duplicate session entries | stream-json quirks |
- Session logs:
~/.happy/logs/2026-01-13-HH-MM-SS-pid-XXXXX.log - Daemon logs:
~/.happy/logs/2026-01-13-HH-MM-SS-pid-XXXXX-daemon.log - Claude Code sessions:
~/.claude/projects/-Users-craigvanheerden-Repos/*.jsonl
# Find crashes
grep "Process exit: code=1" ~/.happy/logs/*.log
# Find DEBUG stderr output
grep "Claude Code stderr" ~/.happy/logs/*-daemon.log
# Find timing around crash
grep -B10 "Process exit: code=1" ~/.happy/logs/*.log-
Wait for next crash with DEBUG enabled- Done, found root cause -
Analyze stderr output for actual error- Found "only prompt commands are supported in streaming mode" - Monitor GitHub issue #16712 for Claude Code fix
- Potential workaround: Don't use
--resumewith--input-format stream-jsontogether - Alternative: Start fresh session instead of resuming when in stream-json mode
Instead of resuming, start a fresh session each time. Loses conversation context but avoids the bug.
May behave differently - needs testing.
GitHub issue #16712 is open. Anthropic may fix this in a future release.
The workaround mentioned in issue #16712 - fragile but works:
- Write tool_result directly to
~/.claude/projects/.../<session>.jsonl - Resume with a text prompt
- GitHub Issue #16712 - Feature request to fix this behavior
- Claude Code Headless Docs - Official documentation
- Claude Agent SDK Spec - Message format specification