fix(terminal): rename Claude terminals only once on initial message (#1366)
* fix(terminal): rename Claude terminals only once on initial message - Add autoNameClaudeTerminals setting (defaults to true) to control whether Claude terminals should be auto-named based on first message - Add claudeNamedOnce flag to terminal store to track if terminal has already been renamed (prevents repeated renames on each message) - Update useAutoNaming hook to only trigger rename once in Claude mode - Add toggle to Developer Tools settings section - Add i18n translations for English and French This fixes the issue where Claude terminals were being renamed on every message sent to Claude instead of just once on the initial message. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(terminal): address code review suggestions - Re-fetch terminal state after async generateTerminalName to avoid stale closure when checking isClaudeMode - Add comment explaining nullish coalescing fallback for new setting 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:
@@ -365,6 +365,31 @@ export function DevToolsSettings({ settings, onSettingsChange }: DevToolsSetting
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Auto-name Claude Terminals Toggle */}
|
||||||
|
<div className="space-y-3 pt-2 border-t border-border">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="auto-name-claude-terminals" className="text-sm font-medium">
|
||||||
|
{t('devtools.autoNameClaude.label', 'Auto-name Claude terminals')}
|
||||||
|
</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t('devtools.autoNameClaude.description', 'Use AI to generate a descriptive name for Claude terminals based on your first message')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* Fallback to true for existing users who don't have this setting in persisted config */}
|
||||||
|
<Switch
|
||||||
|
id="auto-name-claude-terminals"
|
||||||
|
checked={settings.autoNameClaudeTerminals ?? true}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
onSettingsChange({
|
||||||
|
...settings,
|
||||||
|
autoNameClaudeTerminals: checked
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* YOLO Mode Toggle */}
|
{/* YOLO Mode Toggle */}
|
||||||
<div className="space-y-3 rounded-md border border-amber-500/30 bg-amber-500/5 p-4">
|
<div className="space-y-3 rounded-md border border-amber-500/30 bg-amber-500/5 p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|||||||
@@ -11,51 +11,72 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) {
|
|||||||
const lastCommandRef = useRef<string>('');
|
const lastCommandRef = useRef<string>('');
|
||||||
const autoNameTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const autoNameTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const autoNameTerminals = useSettingsStore((state) => state.settings.autoNameTerminals);
|
const autoNameTerminals = useSettingsStore((state) => state.settings.autoNameTerminals);
|
||||||
|
const autoNameClaudeTerminals = useSettingsStore((state) => state.settings.autoNameClaudeTerminals);
|
||||||
const terminal = useTerminalStore((state) => state.terminals.find((t) => t.id === terminalId));
|
const terminal = useTerminalStore((state) => state.terminals.find((t) => t.id === terminalId));
|
||||||
const updateTerminal = useTerminalStore((state) => state.updateTerminal);
|
const updateTerminal = useTerminalStore((state) => state.updateTerminal);
|
||||||
|
const setClaudeNamedOnce = useTerminalStore((state) => state.setClaudeNamedOnce);
|
||||||
|
|
||||||
const triggerAutoNaming = useCallback(async () => {
|
const triggerAutoNaming = useCallback(async () => {
|
||||||
if (!autoNameTerminals || terminal?.isClaudeMode || !lastCommandRef.current.trim()) {
|
// Check if we have a command to base the name on
|
||||||
|
if (!lastCommandRef.current.trim()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = lastCommandRef.current.trim();
|
// Handle Claude mode vs regular terminal mode
|
||||||
const commandLower = command.toLowerCase();
|
if (terminal?.isClaudeMode) {
|
||||||
const firstWord = commandLower.split(/\s+/)[0];
|
// In Claude mode: only rename if autoNameClaudeTerminals is enabled AND we haven't named yet
|
||||||
|
if (!autoNameClaudeTerminals || terminal?.claudeNamedOnce) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Regular terminal mode: use the standard autoNameTerminals setting
|
||||||
|
if (!autoNameTerminals) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip very short commands
|
const command = lastCommandRef.current.trim();
|
||||||
|
|
||||||
|
// Skip very short commands/messages
|
||||||
if (command.length < 3) {
|
if (command.length < 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip common shell/navigation commands that don't represent meaningful work.
|
// In Claude mode, messages are natural language prompts, not shell commands
|
||||||
// These commands are too generic to produce useful terminal names - they don't indicate
|
// Skip the shell command filtering since we want to name based on the first prompt
|
||||||
// a specific task or purpose. For example, "git" could be any git operation,
|
if (!terminal?.isClaudeMode) {
|
||||||
// "npm" could be install, run, or test. Meaningful names come from project-specific
|
const commandLower = command.toLowerCase();
|
||||||
// commands like "npm run build:prod" or application-specific scripts.
|
const firstWord = commandLower.split(/\s+/)[0];
|
||||||
const skipCommands = [
|
|
||||||
// Navigation & file listing
|
|
||||||
'ls', 'cd', 'll', 'la', 'pwd', 'dir', 'tree',
|
|
||||||
// Shell control
|
|
||||||
'exit', 'clear', 'cls', 'reset', 'history',
|
|
||||||
// Claude CLI - naming should come from the task description inside Claude, not the launch command
|
|
||||||
'claude',
|
|
||||||
// Common dev tools that are too generic
|
|
||||||
'git', 'npm', 'yarn', 'pnpm', 'node', 'python', 'pip', 'cargo', 'go',
|
|
||||||
'docker', 'kubectl', 'make', 'cmake',
|
|
||||||
// Package managers
|
|
||||||
'brew', 'apt', 'yum', 'pacman', 'choco', 'scoop', 'winget',
|
|
||||||
// Editors
|
|
||||||
'vim', 'nvim', 'nano', 'code', 'cursor',
|
|
||||||
// System commands
|
|
||||||
'cat', 'head', 'tail', 'less', 'more', 'grep', 'find', 'which', 'where',
|
|
||||||
'echo', 'env', 'export', 'set', 'unset', 'alias', 'source',
|
|
||||||
'chmod', 'chown', 'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch',
|
|
||||||
'man', 'help', 'whoami', 'hostname', 'date', 'time', 'top', 'htop', 'ps',
|
|
||||||
];
|
|
||||||
|
|
||||||
if (skipCommands.includes(firstWord)) {
|
// Skip common shell/navigation commands that don't represent meaningful work.
|
||||||
return;
|
// These commands are too generic to produce useful terminal names - they don't indicate
|
||||||
|
// a specific task or purpose. For example, "git" could be any git operation,
|
||||||
|
// "npm" could be install, run, or test. Meaningful names come from project-specific
|
||||||
|
// commands like "npm run build:prod" or application-specific scripts.
|
||||||
|
const skipCommands = [
|
||||||
|
// Navigation & file listing
|
||||||
|
'ls', 'cd', 'll', 'la', 'pwd', 'dir', 'tree',
|
||||||
|
// Shell control
|
||||||
|
'exit', 'clear', 'cls', 'reset', 'history',
|
||||||
|
// Claude CLI - naming should come from the task description inside Claude, not the launch command
|
||||||
|
'claude',
|
||||||
|
// Common dev tools that are too generic
|
||||||
|
'git', 'npm', 'yarn', 'pnpm', 'node', 'python', 'pip', 'cargo', 'go',
|
||||||
|
'docker', 'kubectl', 'make', 'cmake',
|
||||||
|
// Package managers
|
||||||
|
'brew', 'apt', 'yum', 'pacman', 'choco', 'scoop', 'winget',
|
||||||
|
// Editors
|
||||||
|
'vim', 'nvim', 'nano', 'code', 'cursor',
|
||||||
|
// System commands
|
||||||
|
'cat', 'head', 'tail', 'less', 'more', 'grep', 'find', 'which', 'where',
|
||||||
|
'echo', 'env', 'export', 'set', 'unset', 'alias', 'source',
|
||||||
|
'chmod', 'chown', 'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch',
|
||||||
|
'man', 'help', 'whoami', 'hostname', 'date', 'time', 'top', 'htop', 'ps',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (skipCommands.includes(firstWord)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -64,11 +85,18 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) {
|
|||||||
updateTerminal(terminalId, { title: result.data });
|
updateTerminal(terminalId, { title: result.data });
|
||||||
// Sync to main process so title persists across hot reloads
|
// Sync to main process so title persists across hot reloads
|
||||||
window.electronAPI.setTerminalTitle(terminalId, result.data);
|
window.electronAPI.setTerminalTitle(terminalId, result.data);
|
||||||
|
|
||||||
|
// Mark Claude terminal as named once to prevent repeated renames
|
||||||
|
// Re-fetch terminal state after async operation to avoid stale closure
|
||||||
|
const currentTerminal = useTerminalStore.getState().terminals.find((t) => t.id === terminalId);
|
||||||
|
if (currentTerminal?.isClaudeMode) {
|
||||||
|
setClaudeNamedOnce(terminalId, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Terminal] Auto-naming failed:', error);
|
console.warn('[Terminal] Auto-naming failed:', error);
|
||||||
}
|
}
|
||||||
}, [autoNameTerminals, terminal?.isClaudeMode, terminal?.cwd, cwd, terminalId, updateTerminal]);
|
}, [autoNameTerminals, autoNameClaudeTerminals, terminal?.isClaudeMode, terminal?.claudeNamedOnce, terminal?.cwd, cwd, terminalId, updateTerminal, setClaudeNamedOnce]);
|
||||||
|
|
||||||
const handleCommandEnter = useCallback((command: string) => {
|
const handleCommandEnter = useCallback((command: string) => {
|
||||||
lastCommandRef.current = command;
|
lastCommandRef.current = command;
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export interface Terminal {
|
|||||||
isClaudeBusy?: boolean; // Whether Claude Code is actively processing (for visual indicator)
|
isClaudeBusy?: boolean; // Whether Claude Code is actively processing (for visual indicator)
|
||||||
pendingClaudeResume?: boolean; // Whether this terminal has a pending Claude resume (deferred until tab activated)
|
pendingClaudeResume?: boolean; // Whether this terminal has a pending Claude resume (deferred until tab activated)
|
||||||
displayOrder?: number; // Display order for tab persistence (lower = further left)
|
displayOrder?: number; // Display order for tab persistence (lower = further left)
|
||||||
|
claudeNamedOnce?: boolean; // Whether this Claude terminal has been auto-named based on initial message (prevents repeated naming)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TerminalLayout {
|
interface TerminalLayout {
|
||||||
@@ -126,6 +127,7 @@ interface TerminalState {
|
|||||||
setWorktreeConfig: (id: string, config: TerminalWorktreeConfig | undefined) => void;
|
setWorktreeConfig: (id: string, config: TerminalWorktreeConfig | undefined) => void;
|
||||||
setClaudeBusy: (id: string, isBusy: boolean) => void;
|
setClaudeBusy: (id: string, isBusy: boolean) => void;
|
||||||
setPendingClaudeResume: (id: string, pending: boolean) => void;
|
setPendingClaudeResume: (id: string, pending: boolean) => void;
|
||||||
|
setClaudeNamedOnce: (id: string, named: boolean) => void;
|
||||||
clearAllTerminals: () => void;
|
clearAllTerminals: () => void;
|
||||||
setHasRestoredSessions: (value: boolean) => void;
|
setHasRestoredSessions: (value: boolean) => void;
|
||||||
reorderTerminals: (activeId: string, overId: string) => void;
|
reorderTerminals: (activeId: string, overId: string) => void;
|
||||||
@@ -316,8 +318,9 @@ export const useTerminalStore = create<TerminalState>((set, get) => ({
|
|||||||
...t,
|
...t,
|
||||||
isClaudeMode,
|
isClaudeMode,
|
||||||
status: isClaudeMode ? 'claude-active' : 'running',
|
status: isClaudeMode ? 'claude-active' : 'running',
|
||||||
// Reset busy state when leaving Claude mode
|
// Reset busy state and naming flag when leaving Claude mode
|
||||||
isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined
|
isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined,
|
||||||
|
claudeNamedOnce: isClaudeMode ? t.claudeNamedOnce : undefined
|
||||||
}
|
}
|
||||||
: t
|
: t
|
||||||
),
|
),
|
||||||
@@ -364,6 +367,14 @@ export const useTerminalStore = create<TerminalState>((set, get) => ({
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setClaudeNamedOnce: (id: string, named: boolean) => {
|
||||||
|
set((state) => ({
|
||||||
|
terminals: state.terminals.map((t) =>
|
||||||
|
t.id === id ? { ...t, claudeNamedOnce: named } : t
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
clearAllTerminals: () => {
|
clearAllTerminals: () => {
|
||||||
set({ terminals: [], activeTerminalId: null, hasRestoredSessions: false });
|
set({ terminals: [], activeTerminalId: null, hasRestoredSessions: false });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ export const DEFAULT_APP_SETTINGS = {
|
|||||||
// Language preference (default to English)
|
// Language preference (default to English)
|
||||||
language: 'en' as const,
|
language: 'en' as const,
|
||||||
// Anonymous error reporting (Sentry) - enabled by default to help improve the app
|
// Anonymous error reporting (Sentry) - enabled by default to help improve the app
|
||||||
sentryEnabled: true
|
sentryEnabled: true,
|
||||||
|
// Auto-name Claude terminals based on initial message (enabled by default)
|
||||||
|
autoNameClaudeTerminals: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -262,6 +262,10 @@
|
|||||||
"notInstalled": "Not installed",
|
"notInstalled": "Not installed",
|
||||||
"detectedSummary": "Detected on your system:",
|
"detectedSummary": "Detected on your system:",
|
||||||
"noToolsDetected": "No additional tools detected (VS Code and system terminal will be used)",
|
"noToolsDetected": "No additional tools detected (VS Code and system terminal will be used)",
|
||||||
|
"autoNameClaude": {
|
||||||
|
"label": "Auto-name Claude terminals",
|
||||||
|
"description": "Use AI to generate a descriptive name for Claude terminals based on your first message"
|
||||||
|
},
|
||||||
"yoloMode": {
|
"yoloMode": {
|
||||||
"label": "YOLO Mode",
|
"label": "YOLO Mode",
|
||||||
"description": "Start Claude with --dangerously-skip-permissions flag, bypassing all safety prompts. Use with extreme caution.",
|
"description": "Start Claude with --dangerously-skip-permissions flag, bypassing all safety prompts. Use with extreme caution.",
|
||||||
|
|||||||
@@ -262,6 +262,10 @@
|
|||||||
"notInstalled": "Non installé",
|
"notInstalled": "Non installé",
|
||||||
"detectedSummary": "Détecté sur votre système :",
|
"detectedSummary": "Détecté sur votre système :",
|
||||||
"noToolsDetected": "Aucun outil supplémentaire détecté (VS Code et le terminal système seront utilisés)",
|
"noToolsDetected": "Aucun outil supplémentaire détecté (VS Code et le terminal système seront utilisés)",
|
||||||
|
"autoNameClaude": {
|
||||||
|
"label": "Nommer automatiquement les terminaux Claude",
|
||||||
|
"description": "Utiliser l'IA pour générer un nom descriptif pour les terminaux Claude basé sur votre premier message"
|
||||||
|
},
|
||||||
"yoloMode": {
|
"yoloMode": {
|
||||||
"label": "Mode YOLO",
|
"label": "Mode YOLO",
|
||||||
"description": "Démarrer Claude avec le flag --dangerously-skip-permissions, contournant toutes les invites de sécurité. À utiliser avec une extrême prudence.",
|
"description": "Démarrer Claude avec le flag --dangerously-skip-permissions, contournant toutes les invites de sécurité. À utiliser avec une extrême prudence.",
|
||||||
|
|||||||
@@ -283,6 +283,8 @@ export interface AppSettings {
|
|||||||
dangerouslySkipPermissions?: boolean;
|
dangerouslySkipPermissions?: boolean;
|
||||||
// Anonymous error reporting (Sentry) - enabled by default to help improve the app
|
// Anonymous error reporting (Sentry) - enabled by default to help improve the app
|
||||||
sentryEnabled?: boolean;
|
sentryEnabled?: boolean;
|
||||||
|
// Auto-name Claude terminals based on initial message (only triggers once per session)
|
||||||
|
autoNameClaudeTerminals?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-Claude Source Environment Configuration (for auto-claude repo .env)
|
// Auto-Claude Source Environment Configuration (for auto-claude repo .env)
|
||||||
|
|||||||
Reference in New Issue
Block a user