feat: quick-launch new Claude session + terminal improvements - #102
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds quick-launch controls for starting Claude sessions from Projects (Cmd+Enter, Shift+Enter, Cmd+Click), centralizes terminal command dispatch and VS Code opening via bundle id, exposes IPC/preload APIs for launching sessions and retrieving terminal CWD, moves terminal launch settings into General, and makes settings popup scrollable and non-auto-closing. Changes
Sequence DiagramsequenceDiagram
participant User
participant Renderer as Renderer (Switcher UI)
participant Main as Main Process
participant PTY as CodeV PTY
participant TerminalApp as External Terminal / IDE
User->>Renderer: Select project + Cmd+Enter / Shift+Enter / Cmd+Click
Renderer->>Renderer: determine launch mode (external / codev / codev-terminal)
Renderer->>Main: ipc: launch-new-claude-session(projectPath) or launch-new-claude-session-in-codev(projectPath)
Main->>Main: read settings (terminalApp, terminalMode) and choose launcher
alt launch into CodeV PTY
Main->>Main: invoke registered CodeV callback
Main->>PTY: spawnTerminal() if PTY missing, then write cd '<projectPath>' && clear && claude\n
else launch external terminal or IDE
Main->>TerminalApp: run per-terminal launcher (AppleScript / cmux / open -b)
TerminalApp->>TerminalApp: create window/tab/workspace and run cd '<projectPath>' && claude
end
TerminalApp-->>User: Claude session starts (or PTY shows session)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regex special characters (+, *, ?, etc.) in search input caused react-highlight-words to throw, crashing the React render and producing a black screen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12af7ae to
a73eacd
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cmd+Enter: launch in default Launch Terminal - Shift+Enter: launch in CodeV terminal - Cmd+Click: launch in default Launch Terminal - Supports all 6 terminals: iTerm2, Ghostty, Terminal.app, cmux, VS Code (URI handler), CodeV embedded Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- iTerm2/Terminal.app: pgrep to detect cold start, reuse default window instead of creating extra one - Ghostty/iTerm2/Terminal.app: move activate after window creation in window mode - Cmd+Click: use onClickCapture to intercept before react-select fires onChange Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resume and new-session now share the same terminal launch logic. Removes ~90 lines of duplication. Cold-start pgrep fix now applies to resume as well. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
execFile('code') launches via CLI wrapper, causing macOS to
show a transient extra Dock icon. Using open -b with bundle
ID routes through LaunchServices, reusing the existing icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Launch Terminal + Launch Mode moved from Sessions-only to General section (visible on all tabs) - Claude Session Launch shortcuts shown in Projects section - Settings popup: add maxHeight + scroll, disable auto-close on outside click (toggle via Settings button only) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Terminal tab: 'Claude in Terminal' button to launch new Claude session in external terminal (uses PTY cwd) - Fix #99: use visibility:hidden instead of display:none to preserve xterm layout, eliminates re-fit flash - Fix lsof -a flag for correct cwd detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Position absolute bottom-right, semi-transparent, hover to reveal. Terminal gets full height back. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DropdownIndicator shows Cmd+Enter hint in Projects search - Bump version to 1.0.68 - Changelog for all changes in this PR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect claude child process via pgrep, send Ctrl+C twice to exit, then launch new session after delay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
5 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/claude-session-utility.ts">
<violation number="1" location="src/claude-session-utility.ts:1266">
P2: Inconsistent AppleScript escaping: the `terminal` case escapes both `\` and `"`, but the `iterm2` case only escapes `"`. Both embed the command in an AppleScript double-quoted string, so the escaping should match.</violation>
<violation number="2" location="src/claude-session-utility.ts:1317">
P1: Regression: the original `execFile('code', [projectPath])` was shell-safe. The replacement `exec(\`open -b ...\`)` passes `projectPath` through the shell with only spaces escaped. Use `execFile('open', ['-b', bundleId, projectPath])` to restore safe argument passing.</violation>
</file>
<file name="src/switcher-ui.tsx">
<violation number="1" location="src/switcher-ui.tsx:1404">
P1: Stale `launchClaudeRef` causes wrong action on next Enter. If Cmd+Enter is pressed when no option is focused (empty results), `onChange` never fires and the ref isn't cleared. The next plain Enter will read the stale value and launch Claude instead of opening the IDE.
Clear the ref at the top of `onKeyDown` so any subsequent keypress resets stale state.</violation>
</file>
<file name="docs/quick-launch-claude-session-design.md">
<violation number="1" location="docs/quick-launch-claude-session-design.md:202">
P3: The VS Code launch example uses `code "<project-path>"`, but the PR itself notes that `code` CLI was replaced with `open -b <bundleId>` to avoid spawning an extra Dock icon. Consider updating the design doc to reflect the actual implementation so future readers don't reintroduce the issue.</violation>
</file>
<file name="src/main.ts">
<violation number="1" location="src/main.ts:1084">
P1: Only spaces are escaped in the path written to the PTY shell. Paths with parentheses, `$`, quotes, or other shell metacharacters will break the `cd` command or cause unintended evaluation. Wrap the path in single quotes instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/switcher-ui.tsx (1)
1170-1273:⚠️ Potential issue | 🟠 MajorAdd
autoEscapeto the PR badge and terminal badgeHighlightercomponents.The PR badge Highlighter (line ~1220) and terminal badge Highlighter (line ~1261) are missing the
autoEscapeattribute. Unlike the other Highlighter components in this block (project name, custom titles, branches), these two will crash when search input contains regex special characters such as[or(.Required additions
<Highlighter searchWords={searchWords} + autoEscape textToHighlight={`PR #${prInfo.prNumber}`}<Highlighter searchWords={sessionSearchValue.split(/\s+/).filter(Boolean)} + autoEscape textToHighlight={badge}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/switcher-ui.tsx` around lines 1170 - 1273, The PR badge and terminal badge Highlighter components are missing the autoEscape prop which causes crashes on regex-special characters; update the Highlighter inside the prLinks render (the one rendering <Highlighter searchWords={searchWords} textToHighlight={`PR #${prInfo.prNumber}`} ... />) to include autoEscape, and also add autoEscape to the terminal badge Highlighter (the one rendering <Highlighter searchWords={sessionSearchValue.split(/\s+/).filter(Boolean)} textToHighlight={badge} ... />) so both Highlighter usages escape user search input before highlighting.
🧹 Nitpick comments (4)
docs/quick-launch-claude-session-design.md (1)
1-244: LGTM! Well-structured design document with clear phases, user flows, and implementation details.📝 Optional: Add language specifiers to silence markdownlint warnings
The fenced code blocks at lines 88 and 114 are pseudo-UI mockups. Adding
textas the language specifier would silence the linter:-``` +```text fred-ff [main] /Users/grimmer/git-``` +```text > claude → show project list for selection🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/quick-launch-claude-session-design.md` around lines 1 - 244, Add language specifiers "text" to the two fenced code blocks that are pseudo-UI/mockup examples (the block containing "fred-ff [main] /Users/grimmer/git" and the block starting with "> claude → show project list for selection") so the fences read ```text instead of ``` to silence markdownlint warnings; update these fenced blocks in docs/quick-launch-claude-session-design.md (search for those exact snippet strings to locate them) and commit the change.src/vscode-based-ide-utility.ts (1)
261-266: LGTM! New helper cleanly encapsulates bundle ID logic.Consider using this helper in
openVSCodeBasedIDE(lines 275-282) to eliminate the duplicated bundle ID mapping.♻️ Optional: Use helper in openVSCodeBasedIDE
export const openVSCodeBasedIDE = ( path: string, ifForceReuseWin: boolean = false, ) => { - let app: string; - let bundleId: string; - - if (currentIDEMode === IDEMode.VSCode) { - app = 'vscode'; - bundleId = 'com.microsoft.VSCode'; - } else { - app = 'cursor'; - /** TODO: use osascript -e 'id of app "Cursor"' to get the cursor bundleId instead of hard-coded */ - bundleId = 'com.todesktop.230313mzl4w4u92'; - } + const app = currentIDEMode === IDEMode.VSCode ? 'vscode' : 'cursor'; + const bundleId = getCurrentIDEBundleId();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/vscode-based-ide-utility.ts` around lines 261 - 266, The bundle ID mapping is duplicated in openVSCodeBasedIDE; replace the inline ternary/constant logic there with a call to the new helper getCurrentIDEBundleId() and use its return value wherever the bundle ID is used in openVSCodeBasedIDE (e.g., the variable currently set from currentIDEMode). Keep all existing behavior and tests unchanged—just remove the duplicate mapping and reference getCurrentIDEBundleId() instead.src/switcher-ui.tsx (2)
251-255: Keep the new option callback payload typed.
anyhere drops compile-time checks right where the new cmd-click/delete flow depends onvalueandlabel.Suggested fix
- onDeleteClick?: (data: any) => void, + onDeleteClick?: (data: SelectInputOptionInterface) => void,As per coding guidelines, "Use TypeScript for all components with strict typing".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/switcher-ui.tsx` around lines 251 - 255, The callback payload for delete is currently untyped (uses any) which removes compile-time guarantees; update the OptionUI signature so onDeleteClick expects the specific option type (use SelectInputOptionInterface or the option type carried by OptionProps<SelectInputOptionInterface>) instead of any, e.g., onDeleteClick?: (data: SelectInputOptionInterface) => void, keep onCmdClick?: (path: string) => void as-is, and then update all call sites inside OptionUI to pass the correctly typed object (value/label) so callers get full type checking.
1033-1041: Consider a visible fallback when terminal CWD lookup fails.Right now this button becomes a silent no-op on a
nullCWD. Even a small toast/alert would make the failure mode much clearer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/switcher-ui.tsx` around lines 1033 - 1041, When TerminalTab's onLaunchExternal handler gets a null/undefined cwd from window.electronAPI.terminalGetCwd(), surface a visible fallback instead of doing nothing: detect the missing cwd in the async handler used in the TerminalTab prop (onLaunchExternal) and call the app's notification/toast API (or show an alert) with a clear message like "Unable to determine terminal working directory" before returning; keep the existing launchNewClaudeSession(cwd) call when cwd exists. Reference: TerminalTab component and the functions window.electronAPI.terminalGetCwd and window.electronAPI.launchNewClaudeSession.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/claude-session-utility.ts`:
- Around line 1148-1335: The code injects unescaped projectPath into shell and
AppleScript contexts (in runCommandInTerminal's Ghostty, Terminal, iTerm2 and
cmux branches, and in launchNewClaudeSession/openSessionInVSCode), creating
command-injection risks; fix by normalizing/escaping projectPath for both
AppleScript and shell use: create or reuse an appleScriptString helper to escape
backslashes, quotes and newlines before embedding in AppleScript literals (use
it for Ghostty, Terminal, iTerm2 launchScript templates), and replace naive
space-only escaping in launchNewClaudeSession/openSessionInVSCode and the cmux
--cwd argument with proper shell-argument escaping or, better, use
child_process.execFile/spawn with an array arg for paths (or wrap the escaped
path in single quotes after safely escaping single quotes) so characters like ",
`, $(), ; and newlines cannot break out; update references in
runCommandInTerminal, launchNewClaudeSession and openSessionInVSCode
accordingly.
In `@src/main.ts`:
- Around line 2007-2015: The IPC handlers 'launch-new-claude-session' and
'launch-new-claude-session-in-codev' must reject stale/deleted project paths
before calling launchNewClaudeSession; add a quick exists check (e.g.,
fs.existsSync(projectPath)) at the top of each handler and if the path does not
exist, bail out by sending an error back to the renderer (or logging and
returning) instead of calling launchNewClaudeSession(projectPath, ...), so the
terminal isn't launched with a non-existent cwd.
- Around line 1083-1099: The cd command construction uses naive space-escaping
and is vulnerable to shell injection; update how shortPath/cmd are built by
wrapping the path in single quotes and escaping any embedded single quotes using
the standard shell-safe pattern (replace each ' with '"'"'), so that cmd becomes
something like cd 'escaped-path' && clear && claude, then continue to use
ptyProcess.write(cmd) as before (adjust references in this block where
projectPath, shortPath, and cmd are defined).
In `@src/switcher-ui.tsx`:
- Around line 1402-1419: The launchClaudeRef flag is only cleared in onChange
which means a prior Cmd/Shift+Enter press with no selected result leaves the
flag armed and misfires later; modify the Enter key handling so that when
evt.key === 'Enter' and (evt.metaKey || evt.shiftKey) you still set
launchClaudeRef.current as now but then immediately check whether a
focused/selected result exists and if not reset launchClaudeRef.current = null
(or alternatively clear it in onInputChange/onBlur when there is no selection).
Update the same code paths that reference launchClaudeRef (the key handler block
that sets it, onChange which consumes it, and optionally onInputChange/onBlur)
so the flag is always cleared when Enter does not produce a selection to avoid
accidental Claude launches.
---
Outside diff comments:
In `@src/switcher-ui.tsx`:
- Around line 1170-1273: The PR badge and terminal badge Highlighter components
are missing the autoEscape prop which causes crashes on regex-special
characters; update the Highlighter inside the prLinks render (the one rendering
<Highlighter searchWords={searchWords} textToHighlight={`PR
#${prInfo.prNumber}`} ... />) to include autoEscape, and also add autoEscape to
the terminal badge Highlighter (the one rendering <Highlighter
searchWords={sessionSearchValue.split(/\s+/).filter(Boolean)}
textToHighlight={badge} ... />) so both Highlighter usages escape user search
input before highlighting.
---
Nitpick comments:
In `@docs/quick-launch-claude-session-design.md`:
- Around line 1-244: Add language specifiers "text" to the two fenced code
blocks that are pseudo-UI/mockup examples (the block containing "fred-ff [main]
/Users/grimmer/git" and the block starting with "> claude →
show project list for selection") so the fences read ```text instead of ``` to
silence markdownlint warnings; update these fenced blocks in
docs/quick-launch-claude-session-design.md (search for those exact snippet
strings to locate them) and commit the change.
In `@src/switcher-ui.tsx`:
- Around line 251-255: The callback payload for delete is currently untyped
(uses any) which removes compile-time guarantees; update the OptionUI signature
so onDeleteClick expects the specific option type (use
SelectInputOptionInterface or the option type carried by
OptionProps<SelectInputOptionInterface>) instead of any, e.g., onDeleteClick?:
(data: SelectInputOptionInterface) => void, keep onCmdClick?: (path: string) =>
void as-is, and then update all call sites inside OptionUI to pass the correctly
typed object (value/label) so callers get full type checking.
- Around line 1033-1041: When TerminalTab's onLaunchExternal handler gets a
null/undefined cwd from window.electronAPI.terminalGetCwd(), surface a visible
fallback instead of doing nothing: detect the missing cwd in the async handler
used in the TerminalTab prop (onLaunchExternal) and call the app's
notification/toast API (or show an alert) with a clear message like "Unable to
determine terminal working directory" before returning; keep the existing
launchNewClaudeSession(cwd) call when cwd exists. Reference: TerminalTab
component and the functions window.electronAPI.terminalGetCwd and
window.electronAPI.launchNewClaudeSession.
In `@src/vscode-based-ide-utility.ts`:
- Around line 261-266: The bundle ID mapping is duplicated in
openVSCodeBasedIDE; replace the inline ternary/constant logic there with a call
to the new helper getCurrentIDEBundleId() and use its return value wherever the
bundle ID is used in openVSCodeBasedIDE (e.g., the variable currently set from
currentIDEMode). Keep all existing behavior and tests unchanged—just remove the
duplicate mapping and reference getCurrentIDEBundleId() instead.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 48ed0800-2570-486d-9ff0-390a068eb6e4
📒 Files selected for processing (11)
CHANGELOG.mddocs/quick-launch-claude-session-design.mdpackage.jsonsrc/claude-session-utility.tssrc/electron-api.d.tssrc/main.tssrc/popup.tsxsrc/preload.tssrc/switcher-ui.tsxsrc/terminal-tab.tsxsrc/vscode-based-ide-utility.ts
- VS Code: execFile instead of exec (shell injection, #1) - Stale launchClaudeRef: clear on every keypress (#2) - PTY cd: single-quote path for shell safety (#3) - iTerm2: escape backslash in AppleScript (#4) - Design doc: update to open -b bundleId (#5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add existsSync check to both launch-new-claude-session IPC handlers, matching existing invoke-vscode pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CodeRabbit free plan has rate limits — batch fixes into one push to conserve review quota. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/switcher-ui.tsx (1)
1170-1274:⚠️ Potential issue | 🟠 MajorAdd
autoEscapeto the PR badge and terminal badgeHighlightercomponents.The PR badge and terminal badge
Highlighterinstances in this block omitautoEscape, while the projectName, customTitles, and branchesHighlighterinstances already include it. Searches containing regex special characters like[or(will crash without this attribute.Suggested fix
<Highlighter searchWords={searchWords} + autoEscape textToHighlight={`PR #${prInfo.prNumber}`}<Highlighter searchWords={sessionSearchValue.split(/\s+/).filter(Boolean)} + autoEscape textToHighlight={badge}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/switcher-ui.tsx` around lines 1170 - 1274, The PR badge and terminal badge Highlighter instances (inside the prLinks[session.sessionId] render block where prInfo is used and the terminal badge render where badge is derived from session.isActive/terminalApps[session.sessionId]) are missing the autoEscape prop; add autoEscape to both Highlighter components (set to true) so searches with regex-special characters don't throw.
♻️ Duplicate comments (4)
src/main.ts (2)
2007-2015:⚠️ Potential issue | 🟠 MajorReject stale paths before launching a new session.
These IPC handlers bypass the
existsSyncguard thatinvoke-vscodealready uses. A deleted recent-project entry now opens an external terminal that immediately fails itscd, or leaves the embedded terminal on a broken command.Suggested direction
+const validateProjectPath = (projectPath: string): boolean => { + if (existsSync(projectPath)) return true; + + const window = getSwitcherWindow(); + if (window) { + window.webContents.send('xwin-not-found'); + dialog.showMessageBox(window, { + message: `Path does not exist: ${projectPath}`, + buttons: ['OK'], + defaultId: 0, + cancelId: 1, + }); + } + + return false; +}; @@ ipcMain.on('launch-new-claude-session', async (_event, projectPath: string) => { + if (!validateProjectPath(projectPath)) return; const terminalApp = ((await settings.get('session-terminal-app')) || 'iterm2') as string; const terminalMode = ((await settings.get('session-terminal-mode')) || 'tab') as string; launchNewClaudeSession(projectPath, terminalApp, terminalMode); }); ipcMain.on('launch-new-claude-session-in-codev', (_event, projectPath: string) => { + if (!validateProjectPath(projectPath)) return; launchNewClaudeSession(projectPath, 'codev', 'tab'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.ts` around lines 2007 - 2015, The IPC handlers for 'launch-new-claude-session' and 'launch-new-claude-session-in-codev' must validate the projectPath before calling launchNewClaudeSession; check that fs.existsSync(projectPath) (or the same existsSync guard used by invoke-vscode) and if the path does not exist, return/notify and do not call launchNewClaudeSession. Update the two ipcMain.on callbacks to perform this existence check (and optionally log or send an IPC error back) so deleted/stale recent-project entries are rejected before attempting to open an external or embedded terminal.
1078-1085:⚠️ Potential issue | 🔴 CriticalDon't single-quote the
~prefix.For home-relative paths this builds
cd '~/repo', and the shell will treat~literally instead of expanding it.Shift+Enterinto the CodeV terminal will fail for the common/Users/...case.Suggested fix
- const shortPath = projectPath.replace(os.homedir(), '~'); - const cmd = `cd '${shortPath.replace(/'/g, "'\\''")}' && clear && claude\n`; + const home = os.homedir(); + const rest = projectPath.startsWith(home) ? projectPath.slice(home.length) : null; + const escaped = (rest ?? projectPath).replace(/'/g, "'\\''"); + const cdPath = rest !== null ? `~'${escaped}'` : `'${escaped}'`; + const cmd = `cd ${cdPath} && clear && claude\n`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.ts` around lines 1078 - 1085, The command string in setLaunchInCodevTerminalCallback currently wraps the home-relative shortPath in single quotes which prevents shell expansion of `~`; change the construction of cmd to detect when shortPath startsWith('~') (e.g., '~/...' or exactly '~') and in that case avoid single-quoting the leading tilde: split the path into the leading '~' and the remainder, escape single quotes only in the remainder (using the existing replace(/'/g, "'\\''")), then build cmd as `cd ~/<escaped_remainder> && clear && claude\n` (or `cd ~ && ...` when shortPath === '~'); for all other paths keep the existing single-quoted form to preserve safety. Ensure this logic is implemented where cmd is defined in setLaunchInCodevTerminalCallback using projectPath and shortPath.src/switcher-ui.tsx (1)
1402-1419:⚠️ Potential issue | 🟡 MinorClear
launchClaudeRefwhen Enter doesn't select anything.Resetting on the next keypress still leaves the ref armed for the "Cmd+Enter with no focused option, then click with the mouse" path. In that case
onChangewill still consume the stale launch mode and start Claude instead of opening the project.Suggested fix
launchClaudeRef.current = null; if (evt.key === 'Enter' && (evt.metaKey || evt.shiftKey)) { + if (!document.querySelector('.codev-select__option--is-focused')) { + launchClaudeRef.current = null; + return; + } launchClaudeRef.current = evt.shiftKey ? 'codev' : 'external'; } }} onInputChange={(evt) => { + launchClaudeRef.current = null; setInputValue(evt); }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/switcher-ui.tsx` around lines 1402 - 1419, The launchClaudeRef is left "armed" when the user presses Cmd/Shift+Enter but an option is focused (so Enter will select an option) or when Enter doesn't actually perform a new-session action; update the onKeyDown handler so you only set launchClaudeRef.current = 'codev'|'external' when Enter+meta/shift is pressed AND there is no focused option/selection (e.g., check the component's focusedOption/focusedItem state or fallback to document.activeElement to detect a highlighted suggestion); otherwise explicitly set launchClaudeRef.current = null. Keep the existing onChange behavior unchanged (it should still read and clear launchClaudeRef), and consider also clearing launchClaudeRef in any onMouseDown/onBlur/select handlers that perform normal selection to avoid stale launch modes.src/claude-session-utility.ts (1)
1148-1301:⚠️ Potential issue | 🔴 CriticalProperly escape
projectPathbefore feeding it to shells and AppleScript.The shared launcher still builds
cd "${projectPath}" ..., interpolates raw paths into Ghostty AppleScript, and shells out to cmux with quoted strings. Double quotes do not stop$()/backticks expansion, so a crafted path can still break the launch or execute unintended commands across both the new-session and resume flows.Suggested direction
+const shellQuote = (value: string) => `'${value.replace(/'/g, "'\\''")}'`; +const appleScriptString = (value: string) => + value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n'); @@ - runCommandInTerminal(`cd "${projectPath}" && claude`, 'claude', projectPath, terminalApp, terminalMode); + runCommandInTerminal( + `cd ${shellQuote(projectPath)} && claude`, + 'claude', + projectPath, + terminalApp, + terminalMode, + );Use the same helpers in the Ghostty/Terminal/iTerm2/cmux branches instead of interpolating raw strings.
Also applies to: 1307-1334, 1442-1443, 1657-1658, 1719-1720, 1864-1865
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/claude-session-utility.ts` around lines 1148 - 1301, runCommandInTerminal is interpolating raw projectPath/fullCommand/claudeCmd into shell commands and AppleScript, allowing command substitution via $() or backticks; fix by escaping/quoting these values before insertion and use the existing escaping helper used elsewhere (apply it to projectPath, fullCommand, claudeCmd, and CMUX_CLI) instead of raw interpolation in the Ghostty, Terminal, iTerm2 and cmux branches (look for variables tmpScript, launchScript, escapedCommand, launchInCmux). Ensure AppleScript strings are escaped for quotes/newlines and shell arguments are secured against command substitution (use the helper that safely shell-escapes or AppleScript-escapes strings) in every branch and when constructing cmuxCmd.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/quick-launch-claude-session-design.md`:
- Around line 88-95: The fenced examples (the ASCII UI block shown) and the
inline code span `> claude ` need language labels and whitespace cleanup: add a
language tag (e.g., text) to the triple-backtick fences around the UI example
and the other unlabeled blocks, and rewrite the inline code span to remove the
internal trailing space so it reads `> claude`; apply the same fixes to the
other unlabeled blocks mentioned (the examples around the `> claude` usage at
the other locations).
- Around line 213-219: Update the VS Code bullet so it matches the actual launch
flow: replace "code <path>" with the macOS open invocation "open -b <bundleId>
<path>" while keeping the URI handler "vscode://anthropic.claude-code/open" so
the bullet reads something like: "VS Code: open -b <bundleId> <path> + URI
handler vscode://anthropic.claude-code/open" to reflect the code path used to
avoid the extra Dock icon.
In `@src/main.ts`:
- Around line 1079-1100: The code can return early when ptyProcess is null, so
instead ensure the PTY is created or the launch command is queued before
returning: when calling showSwitcherWindow() and sending 'switch-to-terminal',
if ptyProcess is null, call the function that spawns/initialises the embedded
terminal (or emit the same IPC that creates it) and either await its readiness
or push the prepared cmd into a short-lived queue/buffer tied to ptyProcess;
then run the pgrep/Ctrl+C flow against the real ptyProcess and flush the queued
cmd once ptyProcess is non-null. Update the logic around ptyProcess,
switcherWindow?.webContents.send('switch-to-terminal'), and the cmd/pgrep block
so you never simply return when ptyProcess is missing.
---
Outside diff comments:
In `@src/switcher-ui.tsx`:
- Around line 1170-1274: The PR badge and terminal badge Highlighter instances
(inside the prLinks[session.sessionId] render block where prInfo is used and the
terminal badge render where badge is derived from
session.isActive/terminalApps[session.sessionId]) are missing the autoEscape
prop; add autoEscape to both Highlighter components (set to true) so searches
with regex-special characters don't throw.
---
Duplicate comments:
In `@src/claude-session-utility.ts`:
- Around line 1148-1301: runCommandInTerminal is interpolating raw
projectPath/fullCommand/claudeCmd into shell commands and AppleScript, allowing
command substitution via $() or backticks; fix by escaping/quoting these values
before insertion and use the existing escaping helper used elsewhere (apply it
to projectPath, fullCommand, claudeCmd, and CMUX_CLI) instead of raw
interpolation in the Ghostty, Terminal, iTerm2 and cmux branches (look for
variables tmpScript, launchScript, escapedCommand, launchInCmux). Ensure
AppleScript strings are escaped for quotes/newlines and shell arguments are
secured against command substitution (use the helper that safely shell-escapes
or AppleScript-escapes strings) in every branch and when constructing cmuxCmd.
In `@src/main.ts`:
- Around line 2007-2015: The IPC handlers for 'launch-new-claude-session' and
'launch-new-claude-session-in-codev' must validate the projectPath before
calling launchNewClaudeSession; check that fs.existsSync(projectPath) (or the
same existsSync guard used by invoke-vscode) and if the path does not exist,
return/notify and do not call launchNewClaudeSession. Update the two ipcMain.on
callbacks to perform this existence check (and optionally log or send an IPC
error back) so deleted/stale recent-project entries are rejected before
attempting to open an external or embedded terminal.
- Around line 1078-1085: The command string in setLaunchInCodevTerminalCallback
currently wraps the home-relative shortPath in single quotes which prevents
shell expansion of `~`; change the construction of cmd to detect when shortPath
startsWith('~') (e.g., '~/...' or exactly '~') and in that case avoid
single-quoting the leading tilde: split the path into the leading '~' and the
remainder, escape single quotes only in the remainder (using the existing
replace(/'/g, "'\\''")), then build cmd as `cd ~/<escaped_remainder> && clear &&
claude\n` (or `cd ~ && ...` when shortPath === '~'); for all other paths keep
the existing single-quoted form to preserve safety. Ensure this logic is
implemented where cmd is defined in setLaunchInCodevTerminalCallback using
projectPath and shortPath.
In `@src/switcher-ui.tsx`:
- Around line 1402-1419: The launchClaudeRef is left "armed" when the user
presses Cmd/Shift+Enter but an option is focused (so Enter will select an
option) or when Enter doesn't actually perform a new-session action; update the
onKeyDown handler so you only set launchClaudeRef.current = 'codev'|'external'
when Enter+meta/shift is pressed AND there is no focused option/selection (e.g.,
check the component's focusedOption/focusedItem state or fallback to
document.activeElement to detect a highlighted suggestion); otherwise explicitly
set launchClaudeRef.current = null. Keep the existing onChange behavior
unchanged (it should still read and clear launchClaudeRef), and consider also
clearing launchClaudeRef in any onMouseDown/onBlur/select handlers that perform
normal selection to avoid stale launch modes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 080ddbfe-d50c-4a13-beca-6c62ad3fb786
📒 Files selected for processing (4)
docs/quick-launch-claude-session-design.mdsrc/claude-session-utility.tssrc/main.tssrc/switcher-ui.tsx
- PTY null: spawn terminal before writing command (#1100) - Design doc: VS Code bullet uses open -b bundleId - CLAUDE.md: refine push frequency wording Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (3)
docs/quick-launch-claude-session-design.md (1)
88-95:⚠️ Potential issue | 🟡 MinorResolve markdownlint issues in code fences and inline code span.
Line 88 and Line 114 use unlabeled fenced blocks (MD040), and Line 129 has a padded code span (MD038).
Suggested fix
-``` +```text fred-ff [main] /Users/grimmer/git ┌─────────────────────────────────────────────────────────┐ │ [Open in IDE] [New Claude ▾] [Open in Terminal] │ │ Active sessions: 2 (1 CLI, 1 VS Code) │ └─────────────────────────────────────────────────────────┘ codev [docs/quick-launch-claude-session] /Users/grimmer/git@@
-+textclaude → show project list for selection
claude codev → fuzzy-filter projects, select to launch
claude@ghosttycodev → override terminal for this launch
claude@codevfred-ff → explicitly use CodeV terminal@@ -- Typing `@` after `> claude ` shows a dropdown of terminal options: `@iterm2`, `@ghostty`, `@terminal`, `@cmux`, `@codev` +- Typing `@` after `> claude` shows a dropdown of terminal options: `@iterm2`, `@ghostty`, `@terminal`, `@cmux`, `@codev`Also applies to: 114-119, 129-129
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/quick-launch-claude-session-design.md` around lines 88 - 95, The markdown has unlabeled fenced code blocks and a padded inline code span; label the three fenced blocks shown (the terminal UI block starting with "fred-ff [main] …", the list block starting with "> claude …", and the final command list) with a language tag such as text (e.g., ```text) and remove the extra trailing space inside the inline code span by changing the padded backticked instance "`> claude `" to "`> claude`"; apply the same fixes to the other occurrences noted (the blocks at the other ranges and the padded code span at line 129).src/main.ts (2)
2012-2028:⚠️ Potential issue | 🟠 MajorAdd
try/catcharound the new launch IPC handlers.Line 2012 and Line 2022 handlers can still throw (settings read / launch dispatch), which risks unhandled failures in the main process path. Wrap both handlers in
try/catchand log consistently.Suggested fix
ipcMain.on('launch-new-claude-session', async (_event, projectPath: string) => { - if (!existsSync(projectPath)) { - console.log('[launch-new-claude-session] path does not exist:', projectPath); - return; - } - const terminalApp = ((await settings.get('session-terminal-app')) || 'iterm2') as string; - const terminalMode = ((await settings.get('session-terminal-mode')) || 'tab') as string; - launchNewClaudeSession(projectPath, terminalApp, terminalMode); + try { + if (!existsSync(projectPath)) { + console.log('[launch-new-claude-session] path does not exist:', projectPath); + return; + } + const terminalApp = ((await settings.get('session-terminal-app')) || 'iterm2') as string; + const terminalMode = ((await settings.get('session-terminal-mode')) || 'tab') as string; + launchNewClaudeSession(projectPath, terminalApp, terminalMode); + } catch (error) { + console.error('[launch-new-claude-session] failed:', error); + } }); ipcMain.on('launch-new-claude-session-in-codev', (_event, projectPath: string) => { - if (!existsSync(projectPath)) { - console.log('[launch-new-claude-session-in-codev] path does not exist:', projectPath); - return; - } - launchNewClaudeSession(projectPath, 'codev', 'tab'); + try { + if (!existsSync(projectPath)) { + console.log('[launch-new-claude-session-in-codev] path does not exist:', projectPath); + return; + } + launchNewClaudeSession(projectPath, 'codev', 'tab'); + } catch (error) { + console.error('[launch-new-claude-session-in-codev] failed:', error); + } });As per coding guidelines "Handle errors with try/catch blocks".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.ts` around lines 2012 - 2028, Wrap the bodies of both ipcMain.on handlers for 'launch-new-claude-session' and 'launch-new-claude-session-in-codev' in try/catch blocks to prevent unhandled exceptions from settings.get or launchNewClaudeSession; keep the existing existsSync check, but move it inside the try (or catch errors thrown there too), and on error call console.error (or the app logger) with a clear message including the handler name, projectPath and the caught error so failures are logged consistently; ensure you still return early on missing paths and re-use the same error logging format for both handlers.
1083-1085:⚠️ Potential issue | 🔴 CriticalHome-directory launch paths can fail because
~is quoted.Line 1084 builds
cd '${shortPath...}'. WhenprojectPathis under the home directory, this becomescd '~/...', and~won’t expand inside single quotes, so launch fails.Suggested fix
- const shortPath = projectPath.replace(os.homedir(), '~'); - const cmd = `cd '${shortPath.replace(/'/g, "'\\''")}' && clear && claude\n`; + const home = os.homedir(); + const rest = projectPath.startsWith(home) ? projectPath.slice(home.length) : null; + const escaped = (rest ?? projectPath).replace(/'/g, "'\\''"); + const cdPath = rest !== null ? `~'${escaped}'` : `'${escaped}'`; + const cmd = `cd ${cdPath} && clear && claude\n`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.ts` around lines 1083 - 1085, The constructed command in the cmd variable uses shortPath (derived from projectPath) inside single quotes, so when shortPath begins with '~' the shell will not expand the tilde and cd will fail; update the logic that builds cmd (near shortPath, projectPath and cmd) to detect a home-tilde prefix and either substitute the tilde with $HOME or avoid single quotes around the leading ~ (or use an expanded absolute path) before escaping quotes for the rest of the path, so the resulting command will correctly cd into home-relative paths when ptyProcess is created.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@docs/quick-launch-claude-session-design.md`:
- Around line 88-95: The markdown has unlabeled fenced code blocks and a padded
inline code span; label the three fenced blocks shown (the terminal UI block
starting with "fred-ff [main] …", the list block starting with "> claude …", and
the final command list) with a language tag such as text (e.g., ```text) and
remove the extra trailing space inside the inline code span by changing the
padded backticked instance "`> claude `" to "`> claude`"; apply the same fixes
to the other occurrences noted (the blocks at the other ranges and the padded
code span at line 129).
In `@src/main.ts`:
- Around line 2012-2028: Wrap the bodies of both ipcMain.on handlers for
'launch-new-claude-session' and 'launch-new-claude-session-in-codev' in
try/catch blocks to prevent unhandled exceptions from settings.get or
launchNewClaudeSession; keep the existing existsSync check, but move it inside
the try (or catch errors thrown there too), and on error call console.error (or
the app logger) with a clear message including the handler name, projectPath and
the caught error so failures are logged consistently; ensure you still return
early on missing paths and re-use the same error logging format for both
handlers.
- Around line 1083-1085: The constructed command in the cmd variable uses
shortPath (derived from projectPath) inside single quotes, so when shortPath
begins with '~' the shell will not expand the tilde and cd will fail; update the
logic that builds cmd (near shortPath, projectPath and cmd) to detect a
home-tilde prefix and either substitute the tilde with $HOME or avoid single
quotes around the leading ~ (or use an expanded absolute path) before escaping
quotes for the rest of the path, so the resulting command will correctly cd into
home-relative paths when ptyProcess is created.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1ccc7ef8-1967-4343-b25b-aa8fbfd317db
📒 Files selected for processing (3)
CLAUDE.mddocs/quick-launch-claude-session-design.mdsrc/main.ts
✅ Files skipped from review due to trivial changes (1)
- CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Design doc for quickly launching new Claude Code sessions from CodeV.
Cmd+Enteron Projects tab → launch new claude session in external terminal (highest ROI, small effort)>command mode with@terminaloverride (power user flexibility)Ref: #66 (item 6)
🤖 On behalf of @grimmerk — generated with Claude Code
Summary by cubic
Launch a new Claude Code session from CodeV in one keystroke and from the Terminal tab, with terminal compatibility and stability fixes. Adds a PR review push frequency guide, addresses #66 (item 6) and fixes #99.
New Features
claudein the default Launch Terminal; Shift+Enter launches in the CodeV terminal; Cmd+Click also launches. Projects search shows a Cmd+Enter hint.open -b <bundleId>+vscode://anthropic.claude-code/open), and CodeV via sharedrunCommandInTerminal.electronAPI.terminalGetCwd). AddselectronAPI.launchNewClaudeSession(path)andelectronAPI.launchNewClaudeSessionInCodev(path); Launch Terminal + Launch Mode moved to General (scrollable settings, no auto-close); title bar renamed to “CodeV”.Bug Fixes
react-select.autoEscapeon allreact-highlight-wordsusages to stop crashes on regex characters in search.lsof -a; when launching in the CodeV terminal, detect a runningclaudechild and send Ctrl+C before starting a new session; spawn the PTY if not yet started before writing the command.open -b <bundleId>; validate project paths before launching; security hardening: useexecFile, quote PTYcdpaths, escape backslashes in AppleScript, clear the launch flag on every keypress, and use full absolute paths in PTYcd(tilde doesn’t expand in single quotes).Written for commit 843ca6e. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation