Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c2adf89
auto-claude: subtask-1-1 - Create terminal-machine.ts XState v5 state…
AndyMik90 Feb 13, 2026
d78fac5
auto-claude: subtask-1-2 - Add TerminalSwapState interface and swapSt…
AndyMik90 Feb 13, 2026
2c63663
auto-claude: subtask-1-3 - Update shared/state-machines/index.ts barr…
AndyMik90 Feb 13, 2026
839893c
auto-claude: subtask-2-1 - Add migratedSession option to resumeClaude…
AndyMik90 Feb 13, 2026
4d74fd1
auto-claude: subtask-2-2 - Update terminal-manager with swap orchestr…
AndyMik90 Feb 13, 2026
96a2648
auto-claude: subtask-2-3 - Add isClaudeMode to TERMINAL_PROFILE_CHANG…
AndyMik90 Feb 13, 2026
531f7f8
auto-claude: subtask-3-1 - Auto-resume Claude session after terminal …
AndyMik90 Feb 13, 2026
aef8a68
auto-claude: subtask-3-2 - Integrate XState terminal machine into ter…
AndyMik90 Feb 13, 2026
1d06ad1
auto-claude: subtask-4-1 - Add i18n translation keys for swap/resume …
AndyMik90 Feb 13, 2026
2062c4f
auto-claude: subtask-4-2 - Write comprehensive unit tests for termina…
AndyMik90 Feb 13, 2026
ad2a319
Fix PR review findings: XState machine wiring, dead code, YOLO mode p…
AndyMik90 Feb 14, 2026
2874cd0
Merge branch 'develop' into auto-claude/229-implement-account-aware-t…
AndyMik90 Feb 14, 2026
dea5aeb
Fix follow-up review: security, dead code, XState wiring completeness
AndyMik90 Feb 14, 2026
962cfd8
Merge branch 'develop' into auto-claude/229-implement-account-aware-t…
AndyMik90 Feb 14, 2026
a4047ed
Fix follow-up review: security, dead code, XState wiring completeness
AndyMik90 Feb 14, 2026
5f66bde
fix: address review findings for PR #1819
AndyMik90 Feb 15, 2026
e7f9908
Merge remote-tracking branch 'origin/develop' into auto-claude/229-im…
AndyMik90 Feb 15, 2026
93d3389
fix: resolve PR #1819 review findings - session ID preservation and I…
AndyMik90 Feb 15, 2026
70af403
fix: address remaining PR #1819 code quality findings
AndyMik90 Feb 15, 2026
0d613f4
fix: address all PR #1819 review findings (round 7)
AndyMik90 Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions apps/frontend/src/main/ipc-handlers/terminal-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ export function registerTerminalHandlers(
id: string;
sessionId?: string;
sessionMigrated?: boolean;
isClaudeMode?: boolean;
dangerouslySkipPermissions?: boolean;
}> = [];

// Process each terminal
Expand Down Expand Up @@ -284,11 +286,19 @@ export function registerTerminalHandlers(
debugLog('[terminal-handlers:CLAUDE_PROFILE_SET_ACTIVE] Session migration result:', migrationResult);
}

// Store YOLO mode flag server-side for migrated sessions
// (consumed by resumeClaudeAsync when the new terminal resumes)
if (sessionMigrated && terminal.claudeSessionId && terminal.dangerouslySkipPermissions) {
terminalManager.storeMigratedSessionFlag(terminal.claudeSessionId, terminal.dangerouslySkipPermissions);
}

// All terminals need refresh (PTY env vars can't be updated)
terminalsNeedingRefresh.push({
id: terminal.id,
sessionId: terminal.claudeSessionId,
sessionMigrated
sessionMigrated,
isClaudeMode: terminal.isClaudeMode,
dangerouslySkipPermissions: terminal.dangerouslySkipPermissions
});
}

Expand Down Expand Up @@ -613,9 +623,9 @@ export function registerTerminalHandlers(

ipcMain.on(
IPC_CHANNELS.TERMINAL_RESUME_CLAUDE,
(_, id: string, sessionId?: string) => {
(_, id: string, sessionId?: string, options?: { migratedSession?: boolean }) => {
// Use async version to avoid blocking main process during CLI detection
terminalManager.resumeClaudeAsync(id, sessionId).catch((error) => {
terminalManager.resumeClaudeAsync(id, sessionId, options).catch((error) => {
console.warn('[terminal-handlers] Failed to resume Claude:', error);
});
}
Expand Down
16 changes: 12 additions & 4 deletions apps/frontend/src/main/terminal/claude-integration-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1386,7 +1386,8 @@ export async function invokeClaudeAsync(
export async function resumeClaudeAsync(
terminal: TerminalProcess,
sessionId: string | undefined,
getWindow: WindowGetter
getWindow: WindowGetter,
options?: { migratedSession?: boolean }
): Promise<void> {
// Track terminal state for cleanup on error
const wasClaudeMode = terminal.isClaudeMode;
Expand Down Expand Up @@ -1419,12 +1420,19 @@ export async function resumeClaudeAsync(
// and we don't want stale IDs persisting through SessionHandler.persistSessionAsync().
terminal.claudeSessionId = undefined;

// Deprecation warning for callers still passing sessionId
if (sessionId) {
// Deprecation warning for callers still passing sessionId (skip for migrated sessions)
if (sessionId && !options?.migratedSession) {
console.warn('[ClaudeIntegration:resumeClaudeAsync] sessionId parameter is deprecated and ignored; using claude --continue instead');
}

const command = `${pathPrefix}${escapedClaudeCmd} --continue`;
if (options?.migratedSession) {
debugLog('[ClaudeIntegration:resumeClaudeAsync] Post-swap resume for terminal:', terminal.id);
}

// Preserve YOLO mode flag from terminal's stored state
const extraFlags = terminal.dangerouslySkipPermissions ? YOLO_MODE_FLAG : '';

const command = `${pathPrefix}${escapedClaudeCmd} --continue${extraFlags}`;

// Use PtyManager.writeToPty for safer write with error handling
PtyManager.writeToPty(terminal, `${command}\r`);
Expand Down
41 changes: 36 additions & 5 deletions apps/frontend/src/main/terminal/terminal-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type {
TerminalProcess,
WindowGetter,
TerminalOperationResult,
TerminalProfileChangeInfo
TerminalProfileChangeInfo,
TerminalSwapState
} from './types';
import * as PtyManager from './pty-manager';
import * as SessionHandler from './session-handler';
Expand All @@ -26,6 +27,8 @@ export class TerminalManager {
private saveTimer: NodeJS.Timeout | null = null;
private lastNotifiedRateLimitReset: Map<string, string> = new Map();
private eventCallbacks: TerminalEventHandler.EventHandlerCallbacks;
/** Server-side storage for YOLO mode flags during profile migration (sessionId → flag) */
private migratedSessionFlags: Map<string, boolean> = new Map();
Comment on lines +29 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: migratedSessionFlags entries may linger if a migrated session is never resumed.

Entries are consumed in resumeClaudeAsync (line 241) and bulk-cleared in killAll (line 119), but if a terminal is destroyed individually before resuming (e.g., user closes it), the stale entry persists until the next killAll. Practically this is a handful of Map entries at most, so low impact, but consider also clearing entries in destroy() if the terminal had a pending flag.

🤖 Prompt for AI Agents
In `@apps/frontend/src/main/terminal/terminal-manager.ts` around lines 29 - 30,
migratedSessionFlags can retain entries if a terminal with a pending migration
flag is destroyed without resuming; update the Terminal.destroy (or equivalent
terminal cleanup) to check for and remove the session's key from
migratedSessionFlags (same Map referenced by resumeClaudeAsync and killAll) when
a terminal is torn down, ensuring any sessionId associated with this terminal is
cleared to avoid stale entries.


constructor(getWindow: WindowGetter) {
this.getWindow = getWindow;
Expand Down Expand Up @@ -223,13 +226,32 @@ export class TerminalManager {
/**
* Resume Claude in a terminal asynchronously (non-blocking)
*/
async resumeClaudeAsync(id: string, sessionId?: string): Promise<void> {
async resumeClaudeAsync(id: string, sessionId?: string, options?: { migratedSession?: boolean }): Promise<void> {
const terminal = this.terminals.get(id);
if (!terminal) {
return;
}

await ClaudeIntegration.resumeClaudeAsync(terminal, sessionId, this.getWindow);
// For migrated sessions, restore YOLO mode from server-side storage
// (set during profile change in storeMigratedSessionFlag)
if (options?.migratedSession && sessionId) {
const storedFlag = this.migratedSessionFlags.get(sessionId);
if (storedFlag !== undefined) {
terminal.dangerouslySkipPermissions = storedFlag;
this.migratedSessionFlags.delete(sessionId);
}
}

await ClaudeIntegration.resumeClaudeAsync(terminal, sessionId, this.getWindow, options);
}

/**
* Store YOLO mode flag for a session being migrated during profile swap.
* Called from the profile change handler before the renderer recreates terminals.
* The flag is consumed by resumeClaudeAsync when the new terminal resumes.
*/
storeMigratedSessionFlag(sessionId: string, dangerouslySkipPermissions: boolean): void {
this.migratedSessionFlags.set(sessionId, dangerouslySkipPermissions);
}

/**
Expand All @@ -247,11 +269,19 @@ export class TerminalManager {
return;
}

// Determine if this is a post-swap deferred resume
const isPostSwap = terminal.swapState?.isSwapping === true && terminal.swapState?.sessionMigrated === true;

// Clear the pending flag
terminal.pendingClaudeResume = false;

// Clear swap state if this was a post-swap resume
if (isPostSwap) {
terminal.swapState = undefined;
}

// Now actually resume Claude
await ClaudeIntegration.resumeClaudeAsync(terminal, undefined, this.getWindow);
await ClaudeIntegration.resumeClaudeAsync(terminal, undefined, this.getWindow, isPostSwap ? { migratedSession: true } : undefined);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Post-swap deferred resume drops the migrated sessionId.
Line 284 calls resume with migratedSession: true but sessionId is undefined, so migratedSessionFlags won’t be applied and YOLO mode won’t be restored for deferred resumes. Pass the terminal’s sessionId (or fallback inside resumeClaudeAsync).

🐛 Suggested fix
-    await ClaudeIntegration.resumeClaudeAsync(terminal, undefined, this.getWindow, isPostSwap ? { migratedSession: true } : undefined);
+    const resumeSessionId = terminal.claudeSessionId;
+    await ClaudeIntegration.resumeClaudeAsync(
+      terminal,
+      resumeSessionId,
+      this.getWindow,
+      isPostSwap ? { migratedSession: true } : undefined
+    );
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/terminal/terminal-manager.ts` around lines 272 - 285,
The post-swap deferred resume call to ClaudeIntegration.resumeClaudeAsync passes
migratedSession: true but drops the sessionId, so migratedSessionFlags (YOLO
mode) are not applied; update the call site in terminal-manager.ts (the block
that computes isPostSwap and calls ClaudeIntegration.resumeClaudeAsync) to pass
the terminal's sessionId (e.g., terminal.sessionId) when isPostSwap is true, or
alternatively add a fallback in ClaudeIntegration.resumeClaudeAsync to read
sessionId from the terminal object if the sessionId argument is undefined;
ensure the resumeClaudeAsync invocation includes the migratedSession flag plus
the correct sessionId so migratedSessionFlags are restored.


/**
Expand Down Expand Up @@ -387,7 +417,8 @@ export class TerminalManager {
projectPath: terminal.projectPath,
claudeSessionId: terminal.claudeSessionId,
claudeProfileId: terminal.claudeProfileId,
isClaudeMode: terminal.isClaudeMode
isClaudeMode: terminal.isClaudeMode,
dangerouslySkipPermissions: terminal.dangerouslySkipPermissions
});
}

Expand Down
32 changes: 32 additions & 0 deletions apps/frontend/src/main/terminal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,37 @@ export interface TerminalProcess {
shellType?: WindowsShellType;
/** Whether this terminal is waiting for Claude onboarding to complete (login flow) */
awaitingOnboardingComplete?: boolean;
/** Current account swap state (tracks progress of profile switching) */
swapState?: TerminalSwapState;
}

/**
* Phase of a terminal account swap operation
*/
export type TerminalSwapPhase =
| 'capturing'
| 'migrating'
| 'recreating'
| 'resuming';

/**
* Tracks the progress of an account swap for a terminal.
* Enables the main process to track swap progress independently
* of the renderer's XState machine.
*/
export interface TerminalSwapState {
/** Whether a swap is currently in progress */
isSwapping: boolean;
/** Current phase of the swap operation */
phase: TerminalSwapPhase;
/** Profile ID being swapped to */
targetProfileId: string;
/** Profile ID being swapped from */
sourceProfileId: string;
/** Whether the Claude session was successfully migrated */
sessionMigrated: boolean;
/** Error message if the swap failed */
error?: string;
}

/**
Expand Down Expand Up @@ -99,4 +130,5 @@ export interface TerminalProfileChangeInfo {
claudeSessionId?: string;
claudeProfileId?: string;
isClaudeMode: boolean;
dangerouslySkipPermissions?: boolean;
}
6 changes: 3 additions & 3 deletions apps/frontend/src/preload/api/terminal-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface TerminalAPI {
rows?: number
) => Promise<IPCResult<import('../../shared/types').TerminalRestoreResult>>;
clearTerminalSessions: (projectPath: string) => Promise<IPCResult>;
resumeClaudeInTerminal: (id: string, sessionId?: string) => void;
resumeClaudeInTerminal: (id: string, sessionId?: string, options?: { migratedSession?: boolean }) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

TerminalAPI interface is missing dangerouslySkipPermissions in the options type.

The interface declares options?: { migratedSession?: boolean }, but the implementation at line 169 and the ElectronAPI in ipc.ts (line 255) both include dangerouslySkipPermissions?: boolean. Callers using the TerminalAPI type won't see the flag in autocomplete/type hints.

🐛 Proposed fix
-  resumeClaudeInTerminal: (id: string, sessionId?: string, options?: { migratedSession?: boolean }) => void;
+  resumeClaudeInTerminal: (id: string, sessionId?: string, options?: { migratedSession?: boolean; dangerouslySkipPermissions?: boolean }) => void;
🤖 Prompt for AI Agents
In `@apps/frontend/src/preload/api/terminal-api.ts` at line 50, The TerminalAPI
interface's resumeClaudeInTerminal signature omits the
dangerouslySkipPermissions flag in its options type; update the options type for
resumeClaudeInTerminal in TerminalAPI to include dangerouslySkipPermissions?:
boolean (matching the implementation used by resumeClaudeInTerminal and the
ElectronAPI definition) so callers get correct autocomplete and type-checking
for that flag.

activateDeferredClaudeResume: (id: string) => void;
getTerminalSessionDates: (projectPath?: string) => Promise<IPCResult<import('../../shared/types').SessionDateInfo[]>>;
getTerminalSessionsForDate: (
Expand Down Expand Up @@ -166,8 +166,8 @@ export const createTerminalAPI = (): TerminalAPI => ({
clearTerminalSessions: (projectPath: string): Promise<IPCResult> =>
ipcRenderer.invoke(IPC_CHANNELS.TERMINAL_CLEAR_SESSIONS, projectPath),

resumeClaudeInTerminal: (id: string, sessionId?: string): void =>
ipcRenderer.send(IPC_CHANNELS.TERMINAL_RESUME_CLAUDE, id, sessionId),
resumeClaudeInTerminal: (id: string, sessionId?: string, options?: { migratedSession?: boolean }): void =>
ipcRenderer.send(IPC_CHANNELS.TERMINAL_RESUME_CLAUDE, id, sessionId, options),

activateDeferredClaudeResume: (id: string): void =>
ipcRenderer.send(IPC_CHANNELS.TERMINAL_ACTIVATE_DEFERRED_RESUME, id),
Expand Down
23 changes: 13 additions & 10 deletions apps/frontend/src/renderer/hooks/useTerminalProfileChange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { debugLog, debugError } from '../../shared/utils/debug-logger';
* Hook to handle terminal profile change events.
* When a Claude profile switches, all terminals need to be recreated with the new profile's
* environment variables. Terminals with active Claude sessions will have their sessions
* migrated and can be resumed with --resume {sessionId}.
* migrated and automatically resumed with --continue.
*/
export function useTerminalProfileChange(): void {
// Track terminals being recreated to prevent duplicate processing
Expand Down Expand Up @@ -101,19 +101,22 @@ export function useTerminalProfileChange(): void {
newId: newTerminal.id
});

// If there was an active Claude session that was migrated, show a message
// and set up for potential resume
// If there was an active Claude session that was migrated, auto-resume it
if (sessionId && sessionMigrated) {
debugLog('[useTerminalProfileChange] Session migrated, ready for resume:', sessionId);
// Store the session ID so the user can resume if desired
debugLog('[useTerminalProfileChange] Session migrated, auto-resuming:', sessionId);
// Store the session ID for tracking
store.setClaudeSessionId(newTerminal.id, sessionId);
// Set pending resume flag - user can trigger resume from terminal tab
store.setPendingClaudeResume(newTerminal.id, true);
// Send a message to the terminal about the session
window.electronAPI.sendTerminalInput(

// Auto-resume the Claude session with --continue
// YOLO mode (dangerouslySkipPermissions) is preserved server-side by the
// main process during migration (storeMigratedSessionFlag), so resumeClaudeAsync
// will restore it automatically when migratedSession is true
window.electronAPI.resumeClaudeInTerminal(
Comment on lines 112 to 121

This comment was marked as outdated.

newTerminal.id,
`# Profile switched. Previous Claude session available.\n# Run: claude --resume ${sessionId}\n`
sessionId,
{ migratedSession: true }
);
Comment on lines 110 to 125

This comment was marked as outdated.

debugLog('[useTerminalProfileChange] Resume initiated for terminal:', newTerminal.id);
}

} finally {
Expand Down
Loading
Loading