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 {