From 7fda36ad2ed2074490fac1898764b9cbc24b1cc1 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:29:42 +0100 Subject: [PATCH] fix: use --continue instead of --resume for Claude session restoration (#699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 Co-Authored-By: Cursor Bot --------- Co-authored-by: Claude Opus 4.5 Co-authored-by: Cursor Bot --- .../claude-integration-handler.test.ts | 11 ++++--- .../terminal/claude-integration-handler.ts | 32 +++++++++++++------ .../src/main/terminal/terminal-lifecycle.ts | 31 ++++++++---------- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/frontend/src/main/terminal/__tests__/claude-integration-handler.test.ts b/apps/frontend/src/main/terminal/__tests__/claude-integration-handler.test.ts index fa61e8b3..a72f5550 100644 --- a/apps/frontend/src/main/terminal/__tests__/claude-integration-handler.test.ts +++ b/apps/frontend/src/main/terminal/__tests__/claude-integration-handler.test.ts @@ -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; diff --git a/apps/frontend/src/main/terminal/claude-integration-handler.ts b/apps/frontend/src/main/terminal/claude-integration-handler.ts index d82bc09b..27b6865a 100644 --- a/apps/frontend/src/main/terminal/claude-integration-handler.ts +++ b/apps/frontend/src/main/terminal/claude-integration-handler.ts @@ -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 diff --git a/apps/frontend/src/main/terminal/terminal-lifecycle.ts b/apps/frontend/src/main/terminal/terminal-lifecycle.ts index 7f0ceebe..c366ed7e 100644 --- a/apps/frontend/src/main/terminal/terminal-lifecycle.ts +++ b/apps/frontend/src/main/terminal/terminal-lifecycle.ts @@ -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 {