feat: multi-account support (Batch 1) — CLI + CodeV sessions

Merged
GR
4mo ago
+951-156

Run multiple Claude Code accounts (e.g. personal + work) from one machine. Built on the verified fact that CLAUDE_CONFIG_DIR fully isolates a Claude Code account on macOS (docs/multi-account-support-design.md §3).

What it does

  • CLI (via ~/.config/codev/accounts.sh, sourced from your shell rc): claude <account> / claude-<account> launch the official claude under that account's CLAUDE_CONFIG_DIR; bare claude stays the default account; claude-whoami / claude-accounts inspect. All native flags pass through.
  • CodeV Sessions tab: reads every configured account's config dir (not just ~/.claude) and merges into one recency-sorted list; badges non-default accounts (WORK); resumes each session under its own account; per-account active dots, custom titles/branches, and previews; registers status hooks in each account's settings.json.

Is it fully functional?

  • Multi-account: yes — once the registry (~/.config/codev/accounts.json) + accounts.sh are set up and the extra account is logged in. Validated end-to-end (list, WORK badge, account-aware resume, active dots, titles) via yarn make.
  • If you do nothing / single account: CodeV behaves exactly as before. getAccounts() falls back to a single default account when there's no registry, so the feature is entirely opt-in and invisible to single-account users — all pre-PR functionality is preserved.

⚠️ Setup is manual for now (Batch 1)

Account setup (the ~/.config/codev/accounts.json registry + ~/.config/codev/accounts.sh) is hand-created in this batch — CodeV reads the registry but doesn't yet generate it. A point-and-click management UI + codev account CLI is planned (Batch 2). See design doc §11 "How to use it now".

CodeV never touches tokens

CodeV only ever sets CLAUDE_CONFIG_DIR and launches the official claude binary — it never reads OAuth tokens, calls the model, or embeds an agent. This keeps it inside Anthropic's "use the official product" terms (design doc §2).

Commits

  • ee4a546 design doc + registry reader (src/accounts.ts)
  • 57f609b session aggregation + badge + account-aware resume
  • 5f05d9a active dots + enrichment per account
  • 46c49d1 status hooks per account
  • 5cb3c1a same-cwd cross-ref per account
  • 9b45dc7 docs + changelog + version bump (1.0.76)

Caller audit (per shared-change guidance)

src/accounts.ts is a new util (no existing callers). The session-reading changes tag each session with its account; the enrichment/preview/hook callers (main.ts IPC handlers, switcher-ui.tsx) pass full session objects, so accountDir flows through (verified). Graceful fallback to ~/.claude when a session has no account tag (e.g. VS Code).

Deferred

  • VS Code (claude-vscode) sessions can't be account-switched (URI-handler launch) — #121
  • Batch 2: management UI + codev account CLI, per-launch account picker, configurable global-default (nvm alias default-style), pyenv-style folder auto-switch
  • Batch 3: cross-account symlink reuse of global CLAUDE.md / skills

Testing

  • tsc --noEmit clean throughout; each commit is a minimal, focused diff.
  • User-validated (personal + work): session list, WORK badge, account-aware resume, active dots, titles — all correct after yarn make.

🤖 On behalf of @grimmerk — generated with Claude Code


Summary by cubic

Add multi-account support so you can run multiple Claude Code accounts on one machine. CodeV now merges sessions across accounts and resumes each under the correct account using the registry’s configDirEnv; setup is manual (accounts.json + accounts.sh), and single-account users see no change.

  • New Features

    • Sessions tab reads all configured account dirs and shows one recency-sorted list with a badge for non-default accounts; resume/new launches inject CLAUDE_CONFIG_DIR for the right account (VS Code unchanged).
    • Per-account active dots, custom titles/branches, and previews; status hooks are installed in each account’s settings.json and write to a shared status dir.
    • Registry reader for ~/.config/codev/accounts.json with default fallback; optional CLI helpers via a sourced ~/.config/codev/accounts.sh (claude <account>, claude-<account>, claude-whoami, claude-accounts). Docs include a copy-paste example.
  • Bug Fixes

    • Ghostty resume: single-quoted CLAUDE_CONFIG_DIR prefix to avoid breaking its AppleScript string.
    • Initial status: reads each session’s own account dir, fixing stuck purple dots for non-default accounts.
    • Robustness/correctness: resume uses the registry’s configDirEnv (not the scan dir); per-account history reads are isolated; hook install detection checks all accounts; clipboard copy uses pbcopy via execFileSync; accounts infer isDefault from defaultAccount.
    • Formatting: wrapped long lines flagged in review (no behavior changes).

Summary by CodeRabbit

  • New Features
    • Added multi-account support with unified session discovery across accounts and account-specific badges in the Sessions list.
    • Updated resume commands to reopen sessions in the correct account context (non-default accounts only); note: VS Code sessions can’t switch accounts.
    • Extended status hook installation so it’s managed per configured account.
  • Documentation
    • Added multi-account support design documentation and updated the changelog for version 1.0.76.
  • Chores
    • Bumped the application version to 1.0.76.

Follow-up (post-open fixes)

Found while testing non-default (work) account sessions:

  • Ghostty resume fix (7b9705e): the injected CLAUDE_CONFIG_DIR prefix was double-quoted, which broke Ghostty's unescaped initial input:"..." AppleScript string — non-default accounts couldn't resume in Ghostty (iTerm2/Terminal/cmux were fine). Now single-quoted (safe across all terminals).
  • Purple-dot fix (7b9705e): work sessions stayed on the "active-unknown" purple dot after resume because scanInitialStatuses read ~/.claude/projects for every session, so a work session's JSONL wasn't found. Now reads each session's own account dir (getProjectsDir moved to accounts.ts; accountDir threaded through get-session-statuses).
  • Docs (ca8f0be): §11 "How to use it now" now has a copy-paste-ready accounts.json + accounts.sh example.

Minor perf note (non-blocking): getResumeConfigDirEnv calls readClaudeSessions(Number.MAX_SAFE_INTEGER), which copies the full session array before .find() — O(n) per resume click, n≈hundreds, negligible; can later be changed to query the cache directly.