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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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' });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user