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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user