From 10bceac9cb643f55d6b2bfea246e609e83370a84 Mon Sep 17 00:00:00 2001 From: Andy <119136210+AndyMik90@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:25:39 +0100 Subject: [PATCH] fix(terminal): prevent aggressive renaming on Claude invocation (#1147) * fix(terminal): prevent aggressive renaming on Claude invocation Add shouldAutoRenameTerminal() helper that only allows renaming when: - Terminal has default name pattern ("Terminal X") - Terminal doesn't already have a Claude-related title This preserves user-customized terminal names and prevents renaming on every Claude invocation or resume. Co-Authored-By: Claude Opus 4.5 * test(terminal): update tests for shouldAutoRenameTerminal behavior Update finalizeClaudeInvoke tests to use default terminal name pattern ("Terminal X") so renaming logic is tested correctly. Add new tests verifying: - Terminals already named "Claude" are NOT renamed - User-customized terminal names are preserved Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .../claude-integration-handler.test.ts | 61 ++++++++++++++-- .../terminal/claude-integration-handler.ts | 69 ++++++++++++++----- 2 files changed, 106 insertions(+), 24 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 8f44838a..207d0b39 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 @@ -502,9 +502,10 @@ describe('claude-integration-handler - Helper Functions', () => { }); describe('finalizeClaudeInvoke', () => { - it('should set terminal title to "Claude" for default profile', async () => { + it('should set terminal title to "Claude" for default profile when terminal has default name', async () => { const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); - const terminal = createMockTerminal(); + // Use a default terminal name pattern so renaming logic kicks in + const terminal = createMockTerminal({ title: 'Terminal 1' }); const mockWindow = { webContents: { send: vi.fn() } }; @@ -523,7 +524,8 @@ describe('claude-integration-handler - Helper Functions', () => { it('should set terminal title to "Claude (ProfileName)" for non-default profile', async () => { const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); - const terminal = createMockTerminal(); + // Use a default terminal name pattern so renaming logic kicks in + const terminal = createMockTerminal({ title: 'Terminal 2' }); const mockWindow = { webContents: { send: vi.fn() } }; @@ -540,9 +542,10 @@ describe('claude-integration-handler - Helper Functions', () => { expect(terminal.title).toBe('Claude (Work Profile)'); }); - it('should send IPC message to renderer', async () => { + it('should send IPC message to renderer when terminal has default name', async () => { const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); - const terminal = createMockTerminal(); + // Use a default terminal name pattern so renaming logic kicks in + const terminal = createMockTerminal({ title: 'Terminal 3' }); const mockSend = vi.fn(); const mockWindow = { webContents: { send: mockSend } @@ -564,6 +567,54 @@ describe('claude-integration-handler - Helper Functions', () => { ); }); + it('should NOT rename terminal when already named Claude', async () => { + const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); + // Terminal already has Claude title - should NOT be renamed + const terminal = createMockTerminal({ title: 'Claude' }); + const mockSend = vi.fn(); + const mockWindow = { + webContents: { send: mockSend } + }; + + finalizeClaudeInvoke( + terminal, + { name: 'Work Profile', isDefault: false }, + '/tmp/project', + Date.now(), + () => mockWindow as any, + vi.fn() + ); + + // Title should remain unchanged + expect(terminal.title).toBe('Claude'); + // No IPC message should be sent for title change + expect(mockSend).not.toHaveBeenCalled(); + }); + + it('should NOT rename terminal with user-customized name', async () => { + const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); + // User has customized the terminal name - should NOT be renamed + const terminal = createMockTerminal({ title: 'My Custom Terminal' }); + const mockSend = vi.fn(); + const mockWindow = { + webContents: { send: mockSend } + }; + + finalizeClaudeInvoke( + terminal, + undefined, + '/tmp/project', + Date.now(), + () => mockWindow as any, + vi.fn() + ); + + // Title should remain unchanged + expect(terminal.title).toBe('My Custom Terminal'); + // No IPC message should be sent for title change + expect(mockSend).not.toHaveBeenCalled(); + }); + it('should persist session when terminal has projectPath', async () => { const { finalizeClaudeInvoke } = await import('../claude-integration-handler'); const terminal = createMockTerminal({ projectPath: '/tmp/project' }); diff --git a/apps/frontend/src/main/terminal/claude-integration-handler.ts b/apps/frontend/src/main/terminal/claude-integration-handler.ts index 60638699..c9502474 100644 --- a/apps/frontend/src/main/terminal/claude-integration-handler.ts +++ b/apps/frontend/src/main/terminal/claude-integration-handler.ts @@ -102,6 +102,27 @@ interface ProfileInfo { isDefault?: boolean; } +/** + * Check if a terminal should be auto-renamed when Claude is invoked. + * Returns false if: + * - Terminal already has a Claude-related title (already renamed) + * - Terminal has a user-customized name (not "Terminal X" pattern) + * + * This prevents aggressive renaming on every Claude invocation and + * preserves user-customized terminal names. + */ +function shouldAutoRenameTerminal(currentTitle: string): boolean { + // Already has Claude title - don't rename again + if (currentTitle === 'Claude' || currentTitle.startsWith('Claude (')) { + return false; + } + + // Check if it's a default terminal name (Terminal 1, Terminal 2, etc.) + // Only these can be auto-renamed on first Claude invocation + const defaultNamePattern = /^Terminal \d+$/; + return defaultNamePattern.test(currentTitle); +} + /** * Callback type for session capture */ @@ -139,16 +160,19 @@ export function finalizeClaudeInvoke( getWindow: WindowGetter, onSessionCapture: SessionCaptureCallback ): void { - // Set terminal title based on profile - const title = activeProfile && !activeProfile.isDefault - ? `Claude (${activeProfile.name})` - : 'Claude'; - terminal.title = title; + // Only auto-rename if terminal has default name (first Claude invocation) + // This preserves user-customized names and prevents renaming on every invocation + if (shouldAutoRenameTerminal(terminal.title)) { + const title = activeProfile && !activeProfile.isDefault + ? `Claude (${activeProfile.name})` + : 'Claude'; + terminal.title = title; - // Notify renderer of title change - const win = getWindow(); - if (win) { - win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, title); + // Notify renderer of title change + const win = getWindow(); + if (win) { + win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, title); + } } // Persist session if project path is available @@ -528,14 +552,17 @@ export function resumeClaude( terminal.pty.write(`${command}\r`); - // Update terminal title in main process and notify renderer - terminal.title = 'Claude'; - const win = getWindow(); - if (win) { - win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, 'Claude'); + // Only auto-rename if terminal has default name + // This preserves user-customized names and prevents renaming on every resume + if (shouldAutoRenameTerminal(terminal.title)) { + terminal.title = 'Claude'; + const win = getWindow(); + if (win) { + win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, 'Claude'); + } } - // Persist session with updated title + // Persist session if (terminal.projectPath) { SessionHandler.persistSession(terminal); } @@ -702,10 +729,14 @@ export async function resumeClaudeAsync( terminal.pty.write(`${command}\r`); - terminal.title = 'Claude'; - const win = getWindow(); - if (win) { - win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, 'Claude'); + // Only auto-rename if terminal has default name + // This preserves user-customized names and prevents renaming on every resume + if (shouldAutoRenameTerminal(terminal.title)) { + terminal.title = 'Claude'; + const win = getWindow(); + if (win) { + win.webContents.send(IPC_CHANNELS.TERMINAL_TITLE_CHANGE, terminal.id, 'Claude'); + } } if (terminal.projectPath) {