diff --git a/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx b/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx
index 1691f697..0ccef573 100644
--- a/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx
+++ b/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx
@@ -365,6 +365,31 @@ export function DevToolsSettings({ settings, onSettingsChange }: DevToolsSetting
)}
+ {/* Auto-name Claude Terminals Toggle */}
+
diff --git a/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts b/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts
index d9b1122b..e4a1038c 100644
--- a/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts
+++ b/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts
@@ -11,51 +11,72 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) {
const lastCommandRef = useRef('');
const autoNameTimeoutRef = useRef(null);
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 updateTerminal = useTerminalStore((state) => state.updateTerminal);
+ const setClaudeNamedOnce = useTerminalStore((state) => state.setClaudeNamedOnce);
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;
}
- const command = lastCommandRef.current.trim();
- const commandLower = command.toLowerCase();
- const firstWord = commandLower.split(/\s+/)[0];
+ // Handle Claude mode vs regular terminal mode
+ if (terminal?.isClaudeMode) {
+ // 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) {
return;
}
- // Skip common shell/navigation commands that don't represent meaningful work.
- // 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',
- ];
+ // In Claude mode, messages are natural language prompts, not shell commands
+ // Skip the shell command filtering since we want to name based on the first prompt
+ if (!terminal?.isClaudeMode) {
+ const commandLower = command.toLowerCase();
+ const firstWord = commandLower.split(/\s+/)[0];
- if (skipCommands.includes(firstWord)) {
- return;
+ // Skip common shell/navigation commands that don't represent meaningful work.
+ // 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 {
@@ -64,11 +85,18 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) {
updateTerminal(terminalId, { title: result.data });
// Sync to main process so title persists across hot reloads
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) {
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) => {
lastCommandRef.current = command;
diff --git a/apps/frontend/src/renderer/stores/terminal-store.ts b/apps/frontend/src/renderer/stores/terminal-store.ts
index eb4b7dd5..8b156371 100644
--- a/apps/frontend/src/renderer/stores/terminal-store.ts
+++ b/apps/frontend/src/renderer/stores/terminal-store.ts
@@ -94,6 +94,7 @@ export interface Terminal {
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)
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 {
@@ -126,6 +127,7 @@ interface TerminalState {
setWorktreeConfig: (id: string, config: TerminalWorktreeConfig | undefined) => void;
setClaudeBusy: (id: string, isBusy: boolean) => void;
setPendingClaudeResume: (id: string, pending: boolean) => void;
+ setClaudeNamedOnce: (id: string, named: boolean) => void;
clearAllTerminals: () => void;
setHasRestoredSessions: (value: boolean) => void;
reorderTerminals: (activeId: string, overId: string) => void;
@@ -316,8 +318,9 @@ export const useTerminalStore = create((set, get) => ({
...t,
isClaudeMode,
status: isClaudeMode ? 'claude-active' : 'running',
- // Reset busy state when leaving Claude mode
- isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined
+ // Reset busy state and naming flag when leaving Claude mode
+ isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined,
+ claudeNamedOnce: isClaudeMode ? t.claudeNamedOnce : undefined
}
: t
),
@@ -364,6 +367,14 @@ export const useTerminalStore = create((set, get) => ({
}));
},
+ setClaudeNamedOnce: (id: string, named: boolean) => {
+ set((state) => ({
+ terminals: state.terminals.map((t) =>
+ t.id === id ? { ...t, claudeNamedOnce: named } : t
+ ),
+ }));
+ },
+
clearAllTerminals: () => {
set({ terminals: [], activeTerminalId: null, hasRestoredSessions: false });
},
diff --git a/apps/frontend/src/shared/constants/config.ts b/apps/frontend/src/shared/constants/config.ts
index ab915a16..e108a448 100644
--- a/apps/frontend/src/shared/constants/config.ts
+++ b/apps/frontend/src/shared/constants/config.ts
@@ -57,7 +57,9 @@ export const DEFAULT_APP_SETTINGS = {
// Language preference (default to English)
language: 'en' as const,
// 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
};
// ============================================
diff --git a/apps/frontend/src/shared/i18n/locales/en/settings.json b/apps/frontend/src/shared/i18n/locales/en/settings.json
index 698d38f1..6c1942ba 100644
--- a/apps/frontend/src/shared/i18n/locales/en/settings.json
+++ b/apps/frontend/src/shared/i18n/locales/en/settings.json
@@ -262,6 +262,10 @@
"notInstalled": "Not installed",
"detectedSummary": "Detected on your system:",
"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": {
"label": "YOLO Mode",
"description": "Start Claude with --dangerously-skip-permissions flag, bypassing all safety prompts. Use with extreme caution.",
diff --git a/apps/frontend/src/shared/i18n/locales/fr/settings.json b/apps/frontend/src/shared/i18n/locales/fr/settings.json
index fb11b63f..0fcee50e 100644
--- a/apps/frontend/src/shared/i18n/locales/fr/settings.json
+++ b/apps/frontend/src/shared/i18n/locales/fr/settings.json
@@ -262,6 +262,10 @@
"notInstalled": "Non installé",
"detectedSummary": "Détecté sur votre système :",
"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": {
"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.",
diff --git a/apps/frontend/src/shared/types/settings.ts b/apps/frontend/src/shared/types/settings.ts
index 3ca86175..d107a66f 100644
--- a/apps/frontend/src/shared/types/settings.ts
+++ b/apps/frontend/src/shared/types/settings.ts
@@ -283,6 +283,8 @@ export interface AppSettings {
dangerouslySkipPermissions?: boolean;
// Anonymous error reporting (Sentry) - enabled by default to help improve the app
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)