fix: use --continue instead of --resume for Claude session restoration (#699)

* fix: use --continue instead of --resume for Claude session restoration

The Claude session restore system was incorrectly using 'claude --resume session-id'
with internal .jsonl file IDs from ~/.claude/projects/, which aren't valid session names.

Claude Code's --resume flag expects user-named sessions (set via /rename), not
internal session file IDs like 'agent-a02b21e'.

Changed to always use 'claude --continue' which resumes the most recent conversation
in the current directory. This is simpler and more reliable since Auto Claude already
restores terminals to their correct cwd/projectPath.

* test: update test for --continue behavior (sessionId deprecated)

- Updated test to verify resumeClaude always uses --continue
- sessionId parameter is now deprecated and ignored
- claudeSessionId is cleared since --continue doesn't track specific sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: auto-resume only requires isClaudeMode (sessionId deprecated)

Cursor Bot correctly identified that clearing claudeSessionId in
resumeClaude would break auto-resume on subsequent restarts.

The fix: auto-resume condition now only requires storedIsClaudeMode,
not storedClaudeSessionId. Since resumeClaude uses `claude --continue`
which resumes the most recent session automatically, we don't need
to track specific session IDs anymore.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Cursor Bot <cursor@cursor.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor Bot <cursor@cursor.com>
This commit is contained in:
Andy
2026-01-06 21:29:42 +01:00
committed by GitHub
parent 78b80bcaeb
commit 7fda36ad2e
3 changed files with 44 additions and 30 deletions
@@ -369,7 +369,7 @@ describe('claude-integration-handler', () => {
expect(mockPersistSession).toHaveBeenCalledWith(terminal);
});
it('uses the resolved CLI path for resume and continue', async () => {
it('uses --continue regardless of sessionId (sessionId is deprecated)', async () => {
mockGetClaudeCliInvocation.mockReturnValue({
command: '/opt/claude/bin/claude',
env: { PATH: '/opt/claude/bin:/usr/bin' },
@@ -382,19 +382,22 @@ describe('claude-integration-handler', () => {
});
const { resumeClaude } = await import('../claude-integration-handler');
// Even when sessionId is passed, it should be ignored and --continue used
resumeClaude(terminal, 'abc123', () => null);
const resumeCall = vi.mocked(terminal.pty.write).mock.calls[0][0] as string;
expect(resumeCall).toContain("PATH='/opt/claude/bin:/usr/bin' ");
expect(resumeCall).toContain("'/opt/claude/bin/claude' --resume 'abc123'");
expect(terminal.claudeSessionId).toBe('abc123');
expect(resumeCall).toContain("'/opt/claude/bin/claude' --continue");
expect(resumeCall).not.toContain('--resume');
// sessionId is cleared because --continue doesn't track specific sessions
expect(terminal.claudeSessionId).toBeUndefined();
expect(terminal.isClaudeMode).toBe(true);
expect(mockPersistSession).toHaveBeenCalledWith(terminal);
vi.mocked(terminal.pty.write).mockClear();
mockPersistSession.mockClear();
terminal.projectPath = undefined;
terminal.claudeSessionId = undefined;
terminal.isClaudeMode = false;
resumeClaude(terminal, undefined, () => null);
const continueCall = vi.mocked(terminal.pty.write).mock.calls[0][0] as string;
@@ -367,11 +367,19 @@ export function invokeClaude(
}
/**
* Resume Claude with optional session ID
* Resume Claude session in the current directory
*
* Uses `claude --continue` which resumes the most recent conversation in the
* current directory. This is simpler and more reliable than tracking session IDs,
* since Auto Claude already restores terminals to their correct cwd/projectPath.
*
* Note: The sessionId parameter is kept for backwards compatibility but is ignored.
* Claude Code's --resume flag expects user-named sessions (set via /rename), not
* internal session file IDs.
*/
export function resumeClaude(
terminal: TerminalProcess,
sessionId: string | undefined,
_sessionId: string | undefined,
getWindow: WindowGetter
): void {
terminal.isClaudeMode = true;
@@ -383,15 +391,21 @@ export function resumeClaude(
? `PATH=${escapeShellArg(normalizePathForBash(claudeEnv.PATH))} `
: '';
let command: string;
if (sessionId) {
// SECURITY: Escape sessionId to prevent command injection
command = `${pathPrefix}${escapedClaudeCmd} --resume ${escapeShellArg(sessionId)}`;
terminal.claudeSessionId = sessionId;
} else {
command = `${pathPrefix}${escapedClaudeCmd} --continue`;
// Always use --continue which resumes the most recent session in the current directory.
// This is more reliable than --resume with session IDs since Auto Claude already restores
// terminals to their correct cwd/projectPath.
//
// Note: We clear claudeSessionId because --continue doesn't track specific sessions,
// and we don't want stale IDs persisting through SessionHandler.persistSession().
terminal.claudeSessionId = undefined;
// Deprecation warning for callers still passing sessionId
if (_sessionId) {
console.warn('[ClaudeIntegration:resumeClaude] sessionId parameter is deprecated and ignored; using claude --continue instead');
}
const command = `${pathPrefix}${escapedClaudeCmd} --continue`;
terminal.pty.write(`${command}\r`);
// Update terminal title in main process and notify renderer
@@ -23,8 +23,9 @@ import { debugLog, debugError } from '../../shared/utils/debug-logger';
export interface RestoreOptions {
resumeClaudeSession: boolean;
captureSessionId: (terminalId: string, projectPath: string, startTime: number) => void;
/** Callback triggered when a Claude session needs to be resumed */
onResumeNeeded?: (terminalId: string, sessionId: string) => void;
/** Callback triggered when a Claude session needs to be resumed.
* Note: sessionId is deprecated and ignored - resumeClaude uses --continue */
onResumeNeeded?: (terminalId: string, sessionId: string | undefined) => void;
}
/**
@@ -184,15 +185,19 @@ export async function restoreTerminal(
win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, session.id, session.title);
}
// Auto-resume Claude if session was in Claude mode with a session ID
// Use storedIsClaudeMode and storedClaudeSessionId which come from the persisted store,
// Auto-resume Claude if session was in Claude mode
// Use storedIsClaudeMode which comes from the persisted store,
// not the renderer-passed values (renderer always passes isClaudeMode: false)
if (options.resumeClaudeSession && storedIsClaudeMode && storedClaudeSessionId) {
//
// Note: We no longer require storedClaudeSessionId because resumeClaude uses
// `claude --continue` which resumes the most recent session in the directory
// automatically. Session IDs are deprecated and ignored.
if (options.resumeClaudeSession && storedIsClaudeMode) {
terminal.isClaudeMode = true;
terminal.claudeSessionId = storedClaudeSessionId;
debugLog('[TerminalLifecycle] Auto-resuming Claude session:', storedClaudeSessionId);
// Don't set claudeSessionId - it's deprecated and --continue doesn't use it
debugLog('[TerminalLifecycle] Auto-resuming Claude session using --continue');
// Notify renderer of the Claude session so it can update its store
// Notify renderer that we're in Claude mode (session ID is deprecated)
// This prevents the renderer from also trying to resume (duplicate command)
if (win) {
win.webContents.send(IPC_CHANNELS.TERMINAL_CLAUDE_SESSION, terminal.id, storedClaudeSessionId);
@@ -205,20 +210,12 @@ export async function restoreTerminal(
}
// Small delay to ensure PTY is ready before sending resume command
// Note: sessionId parameter is deprecated and ignored by resumeClaude
if (options.onResumeNeeded) {
setTimeout(() => {
options.onResumeNeeded!(terminal.id, storedClaudeSessionId);
}, 500);
}
} else if (storedClaudeSessionId) {
// Keep session ID for manual resume (no auto-resume if not in Claude mode)
terminal.claudeSessionId = storedClaudeSessionId;
debugLog('[TerminalLifecycle] Preserved Claude session ID for manual resume:', storedClaudeSessionId);
// Persist the session ID so it's available even if app closes before periodic save
if (terminal.projectPath) {
SessionHandler.persistSession(terminal);
}
}
return {