Summary
After pressing Escape (or any other abort action) during an active session, the session becomes permanently stuck. Every subsequent message triggers Claude Code to start, initialize fully (MCP connects, SessionStart hook runs), then immediately exit with code 1. The only workaround is to manually kill the session process, losing all conversation context.
Steps to Reproduce
- Start a session via the web UI
- Wait for Claude to respond with an
AskUserQuestion (e.g., permission prompt, radio buttons)
- Press Escape to dismiss/abort
- Send a new message to the same session
- Expected: Fresh Claude Code session starts and processes the message
- Actual: Claude Code starts, runs SessionStart hook successfully, then immediately exits with code 1. Repeats on every subsequent message.
Root Cause
In claudeRemoteLauncher.ts, after an abort causes Claude Code to exit with code 1, the error handler sets waitForMessageBeforeNextLaunch = true and continues the loop — but never clears the stored Claude Code session ID.
On the next loop iteration:
session.sessionId still holds the dead Claude Code session ID (e.g., 1a59715d-...)
resolveClaudeRemoteSessionStartPlan() uses it as startFrom
- The Agent SDK passes
--resume <dead-session-id> to Claude Code
- Claude Code cannot resume an aborted session → exits with code 1
- The cycle repeats indefinitely
Relevant code path (claudeRemoteLauncher.ts):
// Line ~952 — exitCode === 1 handler
} else {
const exitCode = resolveClaudeCodeExitCode(e);
if (exitCode === 1) {
// ... formats error message ...
session.client.sendSessionEvent({ type: 'message', message });
waitForMessageBeforeNextLaunch = true; // ← waits for next message
continue; // ← but session.sessionId still points to dead session!
}
}
And:
// Line ~947 — abortError handler
} else if (abortError) {
if (controller.signal.aborted) {
session.client.sendSessionEvent({ type: 'message', message: 'Aborted by user' });
}
continue; // ← same problem: session ID not cleared
}
Meanwhile, the onSessionReset callback already has the correct pattern:
onSessionReset: () => {
forceNewSession = true;
session.clearSessionId(); // ← this is what the error handlers need
},
Proposed Fix
Add forceNewSession = true and session.clearSessionId() to both error handlers so the next launch creates a fresh Claude Code session:
// In the abortError handler:
} else if (abortError) {
if (controller.signal.aborted) {
session.client.sendSessionEvent({ type: 'message', message: 'Aborted by user' });
}
forceNewSession = true; // ← ADD
session.clearSessionId(); // ← ADD
continue;
}
// In the exitCode === 1 handler:
if (exitCode === 1) {
// ... existing error formatting ...
session.client.sendSessionEvent({ type: 'message', message });
if (controller.signal.aborted) { // ← ADD
forceNewSession = true; // ← ADD
session.clearSessionId(); // ← ADD
}
waitForMessageBeforeNextLaunch = true;
continue;
}
Environment
- Happier CLI:
0.1.0-dev.1775063171.91734 (dev branch)
- Claude Code:
v2.1.69
- Agent SDK:
0.2.56
- Platform: Linux (Docker container)
Debug Evidence
From the session process log after abort + relaunch:
[22:29:36.925] [remote]: launch error {"message":"Claude Code process exited with code 1"...}
[22:29:36.927] [remote]: launch finally
[22:29:36.927] [remote]: launch done
[22:29:36.976] [remote]: launch
[22:29:36.976] [remote]: Continuing existing session: 1a59715d-35d7-433d-b1ba-a890c6c65279 ← dead session
From the Claude Code debug output on the failed resume:
SessionStart hook succeeds: "Vault is unlocked. Credentials expire in 479 minutes."
LSP server manager shut down successfully ← 1ms later, immediate shutdown
MCP server "happier": UNKNOWN connection closed after 0s (cleanly)
SessionEnd fires
cc @leeroybrun
Summary
After pressing Escape (or any other abort action) during an active session, the session becomes permanently stuck. Every subsequent message triggers Claude Code to start, initialize fully (MCP connects, SessionStart hook runs), then immediately exit with code 1. The only workaround is to manually kill the session process, losing all conversation context.
Steps to Reproduce
AskUserQuestion(e.g., permission prompt, radio buttons)Root Cause
In
claudeRemoteLauncher.ts, after an abort causes Claude Code to exit with code 1, the error handler setswaitForMessageBeforeNextLaunch = trueand continues the loop — but never clears the stored Claude Code session ID.On the next loop iteration:
session.sessionIdstill holds the dead Claude Code session ID (e.g.,1a59715d-...)resolveClaudeRemoteSessionStartPlan()uses it asstartFrom--resume <dead-session-id>to Claude CodeRelevant code path (claudeRemoteLauncher.ts):
And:
Meanwhile, the
onSessionResetcallback already has the correct pattern:Proposed Fix
Add
forceNewSession = trueandsession.clearSessionId()to both error handlers so the next launch creates a fresh Claude Code session:Environment
0.1.0-dev.1775063171.91734(dev branch)v2.1.690.2.56Debug Evidence
From the session process log after abort + relaunch:
From the Claude Code debug output on the failed resume:
cc @leeroybrun