feat(multi-account): new-session account picker + claude tab completion (Batch 2c-lite)#126
feat(multi-account): new-session account picker + claude tab completion (Batch 2c-lite)#126grimmerk wants to merge 15 commits into
Conversation
Alt+Cmd+Enter on a project opens a small account picker (arrow keys + Enter, Esc cancels, click works too); the chosen account launches with an explicit CLAUDE_CONFIG_DIR + `command claude`, bypassing the shell dispatcher — mirroring buildResumeCommand. - Plain Cmd+Enter is unchanged: instant launch under the global default (bare `claude` -> dispatcher). Single-account or registry-less setups never see the picker (falls through to a normal launch). - launchNewClaudeSession gains an optional accountLabel; the codev embedded terminal and VS Code paths ignore it (logged) — the picker targets external terminals. - The overlay reuses the data-settings-panel escape hatch so the focus-stealing click handler leaves it alone. - Cross-account RESUME stays deferred to Batch 3 (copy-fork design). tsc 0 errors; 17/17 tests. Bump 1.0.80 + CHANGELOG + design doc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
📝 WalkthroughWalkthroughThis PR adds an optional accountLabel parameter to the new-session launch flow. The IPC contract, preload bridge, main process handler, and session-utility command construction propagate accountLabel to prefix CLAUDE_CONFIG_DIR when launching claude. The switcher UI adds an Option+Cmd+Enter account picker overlay. Docs and version are updated. ChangesMulti-account new-session launch
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant SwitcherUI
participant Preload
participant MainProcess
participant SessionUtility
SwitcherUI->>SwitcherUI: user presses ⌥⌘+Enter, opens account picker
SwitcherUI->>SwitcherUI: pickLaunchAccount(accountLabel)
SwitcherUI->>Preload: launchNewClaudeSession(projectPath, accountLabel)
Preload->>MainProcess: ipcRenderer.send(launch-new-claude-session, projectPath, accountLabel)
MainProcess->>SessionUtility: launchNewClaudeSession(projectPath, terminalApp, terminalMode, accountLabel)
SessionUtility->>SessionUtility: getAccountByLabel(accountLabel)
alt configDirEnv present
SessionUtility->>SessionUtility: build CLAUDE_CONFIG_DIR + command claude
else no accountLabel or no configDirEnv
SessionUtility->>SessionUtility: use default claude command
end
SessionUtility->>SessionUtility: runCommandInTerminal(claudeCmd)
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/claude-session-utility.ts (1)
11-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNamed imports are not alphabetically ordered.
Per coding guidelines, imports should be organized alphabetically. The current order is
getScannableAccounts, getProjectsDir, getAccountByLabel; it should begetAccountByLabel, getProjectsDir, getScannableAccounts.♻️ Proposed fix
import { + getAccountByLabel, getProjectsDir, - getScannableAccounts, - getAccountByLabel, + getScannableAccounts, } from './accounts';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/claude-session-utility.ts` around lines 11 - 15, The named imports in the claude-session-utility module are not alphabetically ordered. Update the import list from accounts so the symbols getAccountByLabel, getProjectsDir, and getScannableAccounts are sorted alphabetically within the same import statement.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/claude-session-utility.ts`:
- Line 1442: The account override is being lost because `runCommandInTerminal`
is called with the literal `'claude'` instead of the constructed `claudeCmd`, so
terminals that use the second argument directly (notably the ghostty and cmux
paths) drop the `CLAUDE_CONFIG_DIR` prefix. Update the `runCommandInTerminal`
call in `claude-session-utility.ts` to pass the `claudeCmd` variable as the
command argument, matching the behavior expected by the ghostty/cmux branches
and preserving the active account override.
---
Nitpick comments:
In `@src/claude-session-utility.ts`:
- Around line 11-15: The named imports in the claude-session-utility module are
not alphabetically ordered. Update the import list from accounts so the symbols
getAccountByLabel, getProjectsDir, and getScannableAccounts are sorted
alphabetically within the same import statement.
🪄 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: 2a98555f-4cbb-4523-880b-1d32e0a904b5
📒 Files selected for processing (8)
CHANGELOG.mddocs/multi-account-support-design.mdpackage.jsonsrc/claude-session-utility.tssrc/electron-api.d.tssrc/main.tssrc/preload.tssrc/switcher-ui.tsx
There was a problem hiding this comment.
All reported issues were addressed across 8 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Search-bar hint becomes '⌘+Enter: New Claude · ⌥⌘+Enter: pick account' when more than one account is registered; single-account users keep the unchanged short hint (no clutter — user's concern). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
One folder routinely hosts sessions from multiple accounts (the real testing pattern), so a folder->account default would guess wrong where the choice matters most; 2e global default + 2c-lite picker cover the need. Feasible (a default, not a constraint) but dropped on value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- CRITICAL: pass claudeCmd (not literal 'claude') as the 2nd arg to runCommandInTerminal — Ghostty/cmux build their launch scripts from it, so the picker's account env prefix was silently dropped there (iTerm2/Terminal.app were unaffected; resume already passed it) - picker: on getAccounts failure, don't guess an identity — log and abort instead of silently launching the global default - hint updates live: re-check the account count on a same-window codev-accounts-changed event (fired by the Settings popup after mutations) and on window focus (covers CLI-side changes) — no app restart needed (user-reported) - hide react-select's default IndicatorSeparator (the stray '|' that looks like a cursor before the hint; user-reported) - docs: mark 2e as shipped in §7 (review nit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
claude-<label> function names already complete natively (command position), but the dispatcher's first arg didn't — register a labels completion for claude, guarded by _comps so an official Claude Code completion (if one ever ships) wins automatically. Independent of appPath. (user request) +1 test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
Registering our claude completion claimed the whole slot, which would have killed zsh's default file completion at every other position (e.g. claude -p ./path<TAB>) — fall back to _files there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
README gains the Multi-Account section: setup, shell commands table, codev account CLI table, UI map, session-probe gotcha. Changelog covers the full PR scope (picker, live hint, completions, separator, 2d drop). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
Removing an account only unregisters it: folder, login, and session history stay on disk, and re-adding the same name reattaches them (verified during #124 testing). Noted in the README CLI table, the Remove button tooltip, and the post-remove notice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
add <name> just registers name -> ~/.claude-<name> (the account's CLAUDE_CONFIG_DIR); Claude Code fills the folder on first login. Noted in the README callout; the UI shows the dir in the add notice and as a hover tooltip on each account row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
'each account also has a function form, e.g. claude-work' read as if only claude-work existed — now says claude work / claude-work are two equivalent forms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
'work' in the footer read like a fixed keyword — derive the example from the user's actual accounts (prefer a non-default one) and phrase it as 'launch any account above by its name'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
The empty-registry fallback showed a hardcoded 'work' example whose commands don't exist; now the example clause only renders when at least one account is registered (default-only shows its real name). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
The anchor's auto-seeded 'personal' label was otherwise permanent (the anchor can't be removed, so remove+add doesn't apply). rename changes only the label — the folder is never moved (dir is stored in the registry) — updates defaultAccount if it pointed at the old name, and regenerates. Completion offers all labels for rename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
Covers: existing-user first open (empty tab, auto-seeded personal), renaming/naming the default up front (add --dir ~/.claude), add-then- login bootstrap, attaching a hand-maintained account from any folder (--dir; convention is only a default), and rename semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017kCK4t5JSVynVZxGcrTGGp
There was a problem hiding this comment.
10 issues found across 9 files (changes from recent commits).
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="docs/multi-account-support-design.md">
<violation number="1" location="docs/multi-account-support-design.md:479">
P2: The 2e description overstates that all 'CodeV resume/launch' operations bypass the dispatcher. In fact, bare ⌘+Enter (no explicit account label) continues to use 'claude', routed through the accounts.sh dispatcher to respect the configurable global default. Only explicit account picks/resumes use 'command claude' to bypass the dispatcher. The wording should be narrowed to 'explicit account resume/launch' to avoid implying the default path also bypasses the dispatcher.</violation>
</file>
<file name="src/claude-session-utility.ts">
<violation number="1" location="src/claude-session-utility.ts:1445">
P1: When `launchNewClaudeSession` is called with an explicit `accountLabel`, a stale or missing label silently falls back to the default account. Because `getAccountByLabel` always returns an account (falling back to default), and `configDirEnv` is `null` for the default account, the picker can end up launching the wrong account with no error or log. Consider validating that the returned account actually matches the requested label and aborting/logging when it does not.
Additionally, the default-account branch uses bare `command claude` without `env -u CLAUDE_CONFIG_DIR`, so an inherited `CLAUDE_CONFIG_DIR` from the parent environment could still route the session to the wrong account. This contradicts the multi-account semantics documented in the design notes.</violation>
</file>
<file name="CHANGELOG.md">
<violation number="1" location="CHANGELOG.md:6">
P2: The changelog overstates the launch behavior for default-account selections. When the user picks the default/anchor account from the picker, the implementation intentionally omits `CLAUDE_CONFIG_DIR` (falling back to bare `command claude` because the default account's identity lives at `~/.claude.json`, not under a `CLAUDE_CONFIG_DIR`). The release note should clarify that only non-default accounts launch with explicit `CLAUDE_CONFIG_DIR`, or reword to avoid implying the default account behaves the same way.</violation>
</file>
<file name="src/popup.tsx">
<violation number="1" location="src/popup.tsx:892">
P2: The footer suggests users can launch accounts by name via shell commands, but it doesn't verify that shell integration is installed. The `accountsShellInstalled` state is already tracked in this component; gating the command example on it (or showing an alternative "install shell integration to use named launches" note) would prevent users from trying commands that don't yet exist.</violation>
</file>
<file name="src/cli/account-manager.ts">
<violation number="1" location="src/cli/account-manager.ts:360">
P2: The zsh completion for `claude` at the first argument position only calls `compadd` for account labels. When the user types a path prefix like `claude ./<TAB>`, no account label matches `./`, so `compadd` returns non-zero and no completions appear because `_files` is never reached. This removes file/path completion for the first argument even though the dispatcher legitimately passes non-label arguments through to Claude.
Use `compadd ${allLabelsForClaude} || _files` at `CURRENT == 2` so that file completion falls back automatically when the current word doesn't match any account label.</violation>
<violation number="2" location="src/cli/account-manager.ts:522">
P2: `renameAccount` changes the account label but leaves `dir` untouched. Because `addAccount` derives the default directory from the label without checking for collisions, renaming `work` → `office` and later adding `work` back silently creates two accounts sharing `~/.claude-work`. This violates the documented model of one config dir per account and can cause confusing shared identity/session behavior.
Consider adding a directory-collision check in `addAccount` (or guarding `renameAccount`) to prevent two labels from mapping to the same `dir`/`configDirEnv`.</violation>
</file>
<file name="src/cli/codev-account.ts">
<violation number="1" location="src/cli/codev-account.ts:108">
P3: The `rename` command reuses `reloadHint()`, which suggests `source ~/.zshrc` as an alternative to opening a new shell. After a rename, however, sourcing the regenerated `accounts.sh` defines the new `claude-<newLabel>` function but leaves the old `claude-<oldLabel>` function callable in the current shell session — shells do not automatically unset functions that disappear from a sourced file. This can mislead users into thinking the rename took full effect when the old label still works. Consider either warning users that a new shell is required for rename to fully take effect, or unsetting the old function during generation.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:64">
P2: The README should note that `claude <TAB>` account-name completion is skipped when another completion is already registered for `claude`, so users with an existing completion do not expect account labels that will not appear.</violation>
<violation number="2" location="README.md:81">
P2: The docs claim that `codev account add <name>` later reattaches everything after removal, but that only holds for accounts that used the default directory. If an account was originally registered with a custom `--dir`, removing it drops that directory mapping from the registry, and a plain `add <name>` (or UI add) will default to `~/.claude-<name>` instead of the original folder. To avoid silently orphaning the old data, the documentation should mention that reattaching a custom-directory account requires supplying `--dir <original-folder>` again.</violation>
<violation number="3" location="README.md:102">
P2: Account picking for new sessions (⌥⌘+Enter) and account-aware resume are not fully supported across all terminal types. The VS Code and CodeV embedded terminal launch paths currently ignore the account label: `launchNewClaudeSession` routes VS Code launches through a URI handler with no account injection, and explicitly skips account overrides for the embedded terminal. For resume, `openSession` does not thread session account info into the VS Code or embedded terminal paths either. Consider adding a caveat in the Sessions/Projects UI table rows noting that full account override is available only for external terminals (iTerm2, Terminal.app, Ghostty, cmux).</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| // Pass claudeCmd as the 2nd arg too — Ghostty/cmux build their launch | ||
| // scripts from it (iTerm2/Terminal.app use fullCommand), so the account | ||
| // env prefix must be present in both. | ||
| runCommandInTerminal(`cd "${projectPath}" && ${claudeCmd}`, claudeCmd, projectPath, terminalApp, terminalMode); |
There was a problem hiding this comment.
P1: When launchNewClaudeSession is called with an explicit accountLabel, a stale or missing label silently falls back to the default account. Because getAccountByLabel always returns an account (falling back to default), and configDirEnv is null for the default account, the picker can end up launching the wrong account with no error or log. Consider validating that the returned account actually matches the requested label and aborting/logging when it does not.
Additionally, the default-account branch uses bare command claude without env -u CLAUDE_CONFIG_DIR, so an inherited CLAUDE_CONFIG_DIR from the parent environment could still route the session to the wrong account. This contradicts the multi-account semantics documented in the design notes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/claude-session-utility.ts, line 1445:
<comment>When `launchNewClaudeSession` is called with an explicit `accountLabel`, a stale or missing label silently falls back to the default account. Because `getAccountByLabel` always returns an account (falling back to default), and `configDirEnv` is `null` for the default account, the picker can end up launching the wrong account with no error or log. Consider validating that the returned account actually matches the requested label and aborting/logging when it does not.
Additionally, the default-account branch uses bare `command claude` without `env -u CLAUDE_CONFIG_DIR`, so an inherited `CLAUDE_CONFIG_DIR` from the parent environment could still route the session to the wrong account. This contradicts the multi-account semantics documented in the design notes.</comment>
<file context>
@@ -1439,7 +1439,10 @@ export const launchNewClaudeSession = (
+ // Pass claudeCmd as the 2nd arg too — Ghostty/cmux build their launch
+ // scripts from it (iTerm2/Terminal.app use fullCommand), so the account
+ // env prefix must be present in both.
+ runCommandInTerminal(`cd "${projectPath}" && ${claudeCmd}`, claudeCmd, projectPath, terminalApp, terminalMode);
};
</file context>
| need is covered by 2e (global default) + 2c-lite (explicit per-launch picker). | ||
| Technically it was feasible (a default, not a constraint — resume always follows | ||
| the session's own account); dropped on value, not feasibility. | ||
| - *2e — ✅ shipped with 2a (PR #123, v1.0.77):* configurable global-default — |
There was a problem hiding this comment.
P2: The 2e description overstates that all 'CodeV resume/launch' operations bypass the dispatcher. In fact, bare ⌘+Enter (no explicit account label) continues to use 'claude', routed through the accounts.sh dispatcher to respect the configurable global default. Only explicit account picks/resumes use 'command claude' to bypass the dispatcher. The wording should be narrowed to 'explicit account resume/launch' to avoid implying the default path also bypasses the dispatcher.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/multi-account-support-design.md, line 479:
<comment>The 2e description overstates that all 'CodeV resume/launch' operations bypass the dispatcher. In fact, bare ⌘+Enter (no explicit account label) continues to use 'claude', routed through the accounts.sh dispatcher to respect the configurable global default. Only explicit account picks/resumes use 'command claude' to bypass the dispatcher. The wording should be narrowed to 'explicit account resume/launch' to avoid implying the default path also bypasses the dispatcher.</comment>
<file context>
@@ -468,9 +468,18 @@ and **(d)** for CodeV, backed by the same `projectMap` in the registry so both a
+ need is covered by 2e (global default) + 2c-lite (explicit per-launch picker).
+ Technically it was feasible (a default, not a constraint — resume always follows
+ the session's own account); dropped on value, not feasibility.
+ - *2e — ✅ shipped with 2a (PR #123, v1.0.77):* configurable global-default —
+ `codev account default <label>` / Settings → Set default routes bare `claude`
+ through the dispatcher `*)` to the chosen account; CodeV resume/launch bypass the
</file context>
| ## 1.0.80 | ||
|
|
||
| - Feat: multi-account Batch 2c-lite — pick the account when launching a new session | ||
| - `⌥⌘+Enter` on a project opens a small account picker (↑↓ / Enter / Esc, or click); the chosen account launches via explicit `CLAUDE_CONFIG_DIR` + `command claude` (correct on iTerm2/Terminal.app/Ghostty/cmux) |
There was a problem hiding this comment.
P2: The changelog overstates the launch behavior for default-account selections. When the user picks the default/anchor account from the picker, the implementation intentionally omits CLAUDE_CONFIG_DIR (falling back to bare command claude because the default account's identity lives at ~/.claude.json, not under a CLAUDE_CONFIG_DIR). The release note should clarify that only non-default accounts launch with explicit CLAUDE_CONFIG_DIR, or reword to avoid implying the default account behaves the same way.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CHANGELOG.md, line 6:
<comment>The changelog overstates the launch behavior for default-account selections. When the user picks the default/anchor account from the picker, the implementation intentionally omits `CLAUDE_CONFIG_DIR` (falling back to bare `command claude` because the default account's identity lives at `~/.claude.json`, not under a `CLAUDE_CONFIG_DIR`). The release note should clarify that only non-default accounts launch with explicit `CLAUDE_CONFIG_DIR`, or reword to avoid implying the default account behaves the same way.</comment>
<file context>
@@ -3,9 +3,13 @@
- Feat: multi-account Batch 2c-lite — pick the account when launching a new session
- - `⌥⌘+Enter` on a project opens a small account picker (↑↓ / Enter / Esc, or click); the chosen account launches via explicit `CLAUDE_CONFIG_DIR` + `command claude`
+ - `⌥⌘+Enter` on a project opens a small account picker (↑↓ / Enter / Esc, or click); the chosen account launches via explicit `CLAUDE_CONFIG_DIR` + `command claude` (correct on iTerm2/Terminal.app/Ghostty/cmux)
- Plain `⌘+Enter` unchanged: opens instantly under the global default; single-account (or registry-less) setups never see the picker
- - Cross-account *resume* stays deferred to Batch 3 (copy-fork design; a session's transcript lives in its own account)
</file context>
| - `⌥⌘+Enter` on a project opens a small account picker (↑↓ / Enter / Esc, or click); the chosen account launches via explicit `CLAUDE_CONFIG_DIR` + `command claude` (correct on iTerm2/Terminal.app/Ghostty/cmux) | |
| - `⌥⌘+Enter` on a project opens a small account picker (↑↓ / Enter / Esc, or click); the chosen non-default account launches via explicit `CLAUDE_CONFIG_DIR` + `command claude` (correct on iTerm2/Terminal.app/Ghostty/cmux); the default account launches via bare `command claude` |
| Registry: ~/.config/codev/accounts.json — each account also has a | ||
| function form, e.g. claude-work | ||
| Registry: ~/.config/codev/accounts.json | ||
| {accounts.length > 0 && |
There was a problem hiding this comment.
P2: The footer suggests users can launch accounts by name via shell commands, but it doesn't verify that shell integration is installed. The accountsShellInstalled state is already tracked in this component; gating the command example on it (or showing an alternative "install shell integration to use named launches" note) would prevent users from trying commands that don't yet exist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/popup.tsx, line 892:
<comment>The footer suggests users can launch accounts by name via shell commands, but it doesn't verify that shell integration is installed. The `accountsShellInstalled` state is already tracked in this component; gating the command example on it (or showing an alternative "install shell integration to use named launches" note) would prevent users from trying commands that don't yet exist.</comment>
<file context>
@@ -877,8 +888,9 @@ const PopupDefaultExample = ({
- Registry: ~/.config/codev/accounts.json — each account also has a
- function form, e.g. claude-work
+ Registry: ~/.config/codev/accounts.json
+ {accounts.length > 0 &&
+ ` — launch any account above by its name: claude ${exampleAccountLabel} and claude-${exampleAccountLabel} are equivalent`}
</div>
</file context>
| if (reg.accounts.some((a) => a.label === newLabel)) { | ||
| throw new Error(`Account "${newLabel}" already exists`); | ||
| } | ||
| acct.label = newLabel; |
There was a problem hiding this comment.
P2: renameAccount changes the account label but leaves dir untouched. Because addAccount derives the default directory from the label without checking for collisions, renaming work → office and later adding work back silently creates two accounts sharing ~/.claude-work. This violates the documented model of one config dir per account and can cause confusing shared identity/session behavior.
Consider adding a directory-collision check in addAccount (or guarding renameAccount) to prevent two labels from mapping to the same dir/configDirEnv.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/account-manager.ts, line 522:
<comment>`renameAccount` changes the account label but leaves `dir` untouched. Because `addAccount` derives the default directory from the label without checking for collisions, renaming `work` → `office` and later adding `work` back silently creates two accounts sharing `~/.claude-work`. This violates the documented model of one config dir per account and can cause confusing shared identity/session behavior.
Consider adding a directory-collision check in `addAccount` (or guarding `renameAccount`) to prevent two labels from mapping to the same `dir`/`configDirEnv`.</comment>
<file context>
@@ -477,6 +500,31 @@ export function removeAccount(label: string): string | undefined {
+ if (reg.accounts.some((a) => a.label === newLabel)) {
+ throw new Error(`Account "${newLabel}" already exists`);
+ }
+ acct.label = newLabel;
+ if (reg.defaultAccount === oldLabel) reg.defaultAccount = newLabel;
+ writeRegistry(reg);
</file context>
| L.push('# claude flags/subcommands still work — just not suggested).'); | ||
| L.push('_claude_codev_accounts() {'); | ||
| L.push(' if (( CURRENT == 2 )); then'); | ||
| L.push(` compadd ${allLabelsForClaude}`); |
There was a problem hiding this comment.
P2: The zsh completion for claude at the first argument position only calls compadd for account labels. When the user types a path prefix like claude ./<TAB>, no account label matches ./, so compadd returns non-zero and no completions appear because _files is never reached. This removes file/path completion for the first argument even though the dispatcher legitimately passes non-label arguments through to Claude.
Use compadd ${allLabelsForClaude} || _files at CURRENT == 2 so that file completion falls back automatically when the current word doesn't match any account label.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/account-manager.ts, line 360:
<comment>The zsh completion for `claude` at the first argument position only calls `compadd` for account labels. When the user types a path prefix like `claude ./<TAB>`, no account label matches `./`, so `compadd` returns non-zero and no completions appear because `_files` is never reached. This removes file/path completion for the first argument even though the dispatcher legitimately passes non-label arguments through to Claude.
Use `compadd ${allLabelsForClaude} || _files` at `CURRENT == 2` so that file completion falls back automatically when the current word doesn't match any account label.</comment>
<file context>
@@ -346,6 +346,29 @@ export function generateAccountsSh(reg: Registry): string {
+ L.push('# claude flags/subcommands still work — just not suggested).');
+ L.push('_claude_codev_accounts() {');
+ L.push(' if (( CURRENT == 2 )); then');
+ L.push(` compadd ${allLabelsForClaude}`);
+ L.push(' else');
+ L.push(' # keep the default file completion for every other position');
</file context>
| L.push(` compadd ${allLabelsForClaude}`); | |
| L.push(` compadd ${allLabelsForClaude} || _files`); |
|
|
||
| > **Add is just a mapping.** `add <name>` registers *name → config folder* (default `~/.claude-<name>` — that folder **is** the account's `CLAUDE_CONFIG_DIR`). Claude Code itself creates and fills the folder on first login (`claude <name>`). | ||
| > | ||
| > **Remove is non-destructive.** It only unregisters that mapping: the account's folder, login, and session history all stay on disk. `codev account add <name>` (or the UI) later reattaches everything — identity and sessions reappear immediately, no re-login needed. |
There was a problem hiding this comment.
P2: The docs claim that codev account add <name> later reattaches everything after removal, but that only holds for accounts that used the default directory. If an account was originally registered with a custom --dir, removing it drops that directory mapping from the registry, and a plain add <name> (or UI add) will default to ~/.claude-<name> instead of the original folder. To avoid silently orphaning the old data, the documentation should mention that reattaching a custom-directory account requires supplying --dir <original-folder> again.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 81:
<comment>The docs claim that `codev account add <name>` later reattaches everything after removal, but that only holds for accounts that used the default directory. If an account was originally registered with a custom `--dir`, removing it drops that directory mapping from the registry, and a plain `add <name>` (or UI add) will default to `~/.claude-<name>` instead of the original folder. To avoid silently orphaning the old data, the documentation should mention that reattaching a custom-directory account requires supplying `--dir <original-folder>` again.</comment>
<file context>
@@ -41,6 +41,69 @@ For the full same-cwd accuracy matrix (detection + switch by launch method and t
+
+> **Add is just a mapping.** `add <name>` registers *name → config folder* (default `~/.claude-<name>` — that folder **is** the account's `CLAUDE_CONFIG_DIR`). Claude Code itself creates and fills the folder on first login (`claude <name>`).
+>
+> **Remove is non-destructive.** It only unregisters that mapping: the account's folder, login, and session history all stay on disk. `codev account add <name>` (or the UI) later reattaches everything — identity and sessions reappear immediately, no re-login needed.
+
+**Common scenarios:**
</file context>
| | Where | What | | ||
| |-------|------| | ||
| | Settings → Accounts | List/add/remove accounts, set the global default, install shell integration | | ||
| | Sessions tab | Sessions from all accounts with account badges; resume uses each session's own account | |
There was a problem hiding this comment.
P2: Account picking for new sessions (⌥⌘+Enter) and account-aware resume are not fully supported across all terminal types. The VS Code and CodeV embedded terminal launch paths currently ignore the account label: launchNewClaudeSession routes VS Code launches through a URI handler with no account injection, and explicitly skips account overrides for the embedded terminal. For resume, openSession does not thread session account info into the VS Code or embedded terminal paths either. Consider adding a caveat in the Sessions/Projects UI table rows noting that full account override is available only for external terminals (iTerm2, Terminal.app, Ghostty, cmux).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 102:
<comment>Account picking for new sessions (⌥⌘+Enter) and account-aware resume are not fully supported across all terminal types. The VS Code and CodeV embedded terminal launch paths currently ignore the account label: `launchNewClaudeSession` routes VS Code launches through a URI handler with no account injection, and explicitly skips account overrides for the embedded terminal. For resume, `openSession` does not thread session account info into the VS Code or embedded terminal paths either. Consider adding a caveat in the Sessions/Projects UI table rows noting that full account override is available only for external terminals (iTerm2, Terminal.app, Ghostty, cmux).</comment>
<file context>
@@ -41,6 +41,69 @@ For the full same-cwd accuracy matrix (detection + switch by launch method and t
+| Where | What |
+|-------|------|
+| Settings → Accounts | List/add/remove accounts, set the global default, install shell integration |
+| Sessions tab | Sessions from all accounts with account badges; resume uses each session's own account |
+| Projects tab: `⌥⌘+Enter` | Pick the account for a new session (`⌘+Enter` stays instant, under the global default) |
+
</file context>
| | `claude-whoami` | Which account bare `claude` resolves to here, plus its auth status | | ||
| | `claude-accounts` | Quick list of configured accounts | | ||
|
|
||
| Tab completion included (zsh): `claude <TAB>` completes account names; `codev account <TAB>` completes subcommands and labels. |
There was a problem hiding this comment.
P2: The README should note that claude <TAB> account-name completion is skipped when another completion is already registered for claude, so users with an existing completion do not expect account labels that will not appear.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 64:
<comment>The README should note that `claude <TAB>` account-name completion is skipped when another completion is already registered for `claude`, so users with an existing completion do not expect account labels that will not appear.</comment>
<file context>
@@ -41,6 +41,69 @@ For the full same-cwd accuracy matrix (detection + switch by launch method and t
+| `claude-whoami` | Which account bare `claude` resolves to here, plus its auth status |
+| `claude-accounts` | Quick list of configured accounts |
+
+Tab completion included (zsh): `claude <TAB>` completes account names; `codev account <TAB>` completes subcommands and labels.
+
+**`codev account` CLI** (the account manager — same generator the Settings UI uses):
</file context>
| Tab completion included (zsh): `claude <TAB>` completes account names; `codev account <TAB>` completes subcommands and labels. | |
| Tab completion included (zsh): `claude <TAB>` completes account names when no other `claude` completion is registered; `codev account <TAB>` completes subcommands and labels. |
|
|
||
| case 'rename': { | ||
| const [oldLabel, newLabel] = rest; | ||
| manager.renameAccount(oldLabel, newLabel); |
There was a problem hiding this comment.
P3: The rename command reuses reloadHint(), which suggests source ~/.zshrc as an alternative to opening a new shell. After a rename, however, sourcing the regenerated accounts.sh defines the new claude-<newLabel> function but leaves the old claude-<oldLabel> function callable in the current shell session — shells do not automatically unset functions that disappear from a sourced file. This can mislead users into thinking the rename took full effect when the old label still works. Consider either warning users that a new shell is required for rename to fully take effect, or unsetting the old function during generation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/cli/codev-account.ts, line 108:
<comment>The `rename` command reuses `reloadHint()`, which suggests `source ~/.zshrc` as an alternative to opening a new shell. After a rename, however, sourcing the regenerated `accounts.sh` defines the new `claude-<newLabel>` function but leaves the old `claude-<oldLabel>` function callable in the current shell session — shells do not automatically unset functions that disappear from a sourced file. This can mislead users into thinking the rename took full effect when the old label still works. Consider either warning users that a new shell is required for rename to fully take effect, or unsetting the old function during generation.</comment>
<file context>
@@ -102,6 +103,14 @@ function main(): number {
+ case 'rename': {
+ const [oldLabel, newLabel] = rest;
+ manager.renameAccount(oldLabel, newLabel);
+ console.log(`✓ Renamed "${oldLabel}" → "${newLabel}" (folder unchanged)`);
+ reloadHint();
</file context>
Summary
Batch 2c-lite (follows #125): pick the account when launching a new Claude session —
⌥⌘+Enteron a project opens a small account picker; plain⌘+Enterstays exactly as before.Design decision (per discussion): the picker is behind a modifier, never forced — quick-launch speed is preserved, and "which account does bare
⌘+Enteruse" remains the global default (codev account default/ Settings → Accounts), which shipped with 2e. Cross-account resume is deliberately NOT here — a session's transcript lives in its own account'sprojects/dir, so "resume under another account" must be an explicit copy-fork (Batch 3), never a link.Behavior
⌘+Enterclaude→ dispatcher)⇧+Enter⌥⌘+Enterdefaultmarker + email shown) → launches with explicitCLAUDE_CONFIG_DIR='…' command claude(dispatcher bypassed, mirroring resume)⌥⌘+Enter, single account / no registryImplementation
launchNewClaudeSession(projectPath, terminalApp, terminalMode, accountLabel?)— with a label, builds the env-prefixedcommand claude(single-quote-escaped); without, bareclaudeas before. VS Code (Multi-account: VS Code (claude-vscode) sessions can't be pinned to a Claude account #121) and the embedded codev terminal ignore the label (logged).launch-new-claude-session+ preload + types gain the optional label.launchClaudeRefgains'external-pick'; the picker overlay reuses thedata-settings-panelescape hatch so the global focus-stealing click handler leaves it alone;Esccloses only the picker (stopPropagation).⌥⌘+Enter.Also in this PR (grew during review/testing)
runCommandInTerminalnow receivesclaudeCmdas its 2nd arg — Ghostty/cmux build their launch scripts from it, so the picked account's env prefix reaches those terminals too (iTerm2/Terminal.app were unaffected; resume already did this).· ⌥⌘+Enter: pick accountonly for multi-account setups, and refreshes after account add/remove (same-windowcodev-accounts-changedevent + window-focus re-check) — no app restart (user-reported).getAccounts()fails, the picker logs and aborts instead of silently launching the global default.claude <name>:claude <TAB>completes account labels; registered only when nothing else completesclaude(_compsguard — a future official completion wins automatically); other argument positions keep zsh's default file completion (_filesfallback).IndicatorSeparator(the stray|before the hint; user-reported).codev accountCLI table, UI map, session-probe gotcha); design doc marks 2e as shipped and drops 2d (folder→account auto-switch) — one folder legitimately hosts sessions from multiple accounts, so a folder default would guess wrong exactly where it matters; 2e global default + this picker cover the need.How to test (needs
yarn make)⌥⌘+Enter→ picker shows personal/work (+default marker, emails).!command claude auth statusshows the work identity (!claude auth statuswould show the global default — see feat(multi-account): codev account CLI + global-default (Batch 2a + 2e) #123's gotcha).Esccloses the picker; clicking the dimmed backdrop closes it;⌘+Enterstill launches instantly.|before the hint is gone.codev account regenerate+ a new shell,claude w<TAB>completeswork;claude -p ./<TAB>still completes files.⌥⌘+Enterand verify with!command claude auth status(this path carried the critical fix).tsc --noEmit0 errors;yarn test18/18.🤖 Generated with Claude Code
Summary by cubic
Adds an optional account picker for new Claude sessions, zsh completion for
claude <TAB>, and acodev account renamecommand. Improves the multi-account UI hints, terminal launch correctness, and docs.New Features
CLAUDE_CONFIG_DIR+command claude(dispatcher bypassed); VS Code and thecodevterminal ignore the override.claude <TAB>completes account labels; registers only if no existingclaudecompletion; other args fall back to_files.codev account rename <old> <new>renames a label without moving the folder and updates the default if needed; completion suggests labels.Bug Fixes
claudeCmdto ensure the account env prefix is preserved.getAccountsfailure instead of launching the default.IndicatorSeparatorin the search bar.Written for commit ff41698. Summary will update on new commits.
Summary by CodeRabbit
New Features
⌥⌘+Enter.⌘+Enterstill uses the default account without prompting.Bug Fixes