Skip to content

feat(claude-accounts): launch a Claude agent on a specific managed account with --account - #22691

Open
fardenghi wants to merge 16 commits into
stablyai:mainfrom
fardenghi:feat/claude-launch-account
Open

fardenghi wants to merge 16 commits into
stablyai:mainfrom
fardenghi:feat/claude-launch-account

Conversation

@fardenghi

Copy link
Copy Markdown

ELI5

Orca can keep several Claude accounts signed in, but only one of them is "the" account on a machine at a time, so every Claude agent Orca starts uses that one. This PR lets you say, per launch, "start this Claude agent on my other account": --account <id|email>. That agent runs on the account you named, the rest of Orca keeps using the active account, and nothing gets switched. Two Claude agents on two accounts can now work side by side. Launches without --account behave exactly as before.

What Changed

Before: Claude account selection is global per host. Activating an account writes its credentials into the shared ~/.claude, and switching is blocked while Claude terminals are live. A script or coordinator agent can't say "start this worker on account X". The only workaround is a raw CLAUDE_CONFIG_DIR=… claude terminal, and Orca doesn't recognize that as a managed agent: it has no hooks, no worker_done and no account safety. See #8770.

After:

orca account list --json                                  # discover ids / emails
orca orchestration worker-start --agent claude --account work@example.com --model … --task …
orca worktree create --name task --agent claude --account <account-id> --prompt "…"

Launch receipts report the resolved account: account: { id, email, mode: 'pinned' | 'active' }.

Mechanism. This is a "pinned launch", scoped to the host runtime lane.

  • Resolution. --account accepts an id, or an email matched case-insensitively.
    • An ambiguous email is refused and the error lists the matching ids.
    • An unknown selector is refused with a pointer to orca account list.
    • If it names the account that is already active, the normal launch path runs unchanged.
  • Config dir. A non-active account launches with CLAUDE_CONFIG_DIR and CLAUDE_SECURESTORAGE_CONFIG_DIR set to that account's existing managed auth dir. Auth env is stripped the same way as on the WSL managed path. This is the same shape as the WSL path and the inactive-preview rate-limit path, so no new credential location is introduced.
    • Linux / Windows: Claude reads and refreshes the managed .credentials.json directly. It is the only store, so nothing is copied or read back.
    • macOS: the managed credential is copied into the keychain item Claude Code 2.1+ derives for that config dir (Claude Code-credentials-<sha256(dir)[:8]>). The unscoped legacy item is never touched. When the account's last pinned PTY exits, the item is read back into the managed store and deleted. The read-back runs only if the managed copy is unchanged since the seed, so a re-auth in between wins, and the identity matches. A small marker next to the auth dir holds a sha256 of the seeded blob (never the secret) so a crash is recovered at the next use.
  • Refresh safety: one owner per account. Refresh tokens are single-use (see fix(claude-auth): make the CLI's scoped Keychain item the single credential owner #17796), so an account must never be refreshed from two stores. A small registry keeps the claims on each account, and each claim is one synchronous check-and-set, so whichever side claims first wins regardless of which async queue it runs in:
    • A pinned launch reserves its account before any await. Host switch, remove and re-authenticate claim the account for their whole operation, including the filesystem and keychain work. Each side refuses while the other holds the claim, with "in use by N terminals launched with --account". Re-auth refuses up front, before any browser login opens.
    • Orca's usage fetch (its OAuth refresh and the usage-panel claude preview) claims the account for the whole fetch. A pinned launch that meets an in-flight fetch waits for it, up to a bound, outside the process-wide auth queue, so unrelated launches and syncs are never delayed. While pinned, the fetch takes a read-only path (on macOS it reads the session's scoped item).
    • Ordinary host Claude terminals now record which managed account they started on. A pinned launch of an account that still has such terminals alive is refused: a later host switch leaves them holding that account's token in memory. Launches without --account never consult this record.
    • Before an account is activated, a pending pinned session is reconciled, so activation never materializes a spent token. Startup re-validates crash markers against the owned auth path.
  • Tracking. Pinned PTYs live in a separate per-account registry, not in the global live-Claude set, so they don't block unrelated host switches. The registry is released through the existing single exit choke point (markClaudePtyExited), persisted in <userData>/claude-pinned-pane-accounts.json, and confirmed against the daemon on restart.
  • Config dir contents. On each pinned launch, idempotently:
    • projects/ is linked to the host's projects/ (a junction on Windows), so resume, session search and usage scanners see one transcript store.
    • settings.json is copied from the host, so Orca's hooks and the statusline load. Without it, worker-start would end in turn_unobserved.
    • .claude.json gets an add-only merge of first-run and trust keys plus oauthAccount, so the pane doesn't stop on onboarding or trust prompts.
  • Capability. A new runtime capability claude.launch-account.v1 lets the CLI refuse --account against an older host. Without it, an older host would silently launch on its active account.

Out of scope (explicit errors, not silent fallbacks):

  • SSH workspaces, --on/remote runtimes and WSL-runtime accounts.
  • Structured (chat) workers: they downgrade to terminal mode with a new reason, pinned_claude_account.
  • The renderer pty:spawn lane refuses a pinned launch.

With Agent Teams enabled, a pinned launch uses in-process teammates, which share the pinned credentials, instead of separate-terminal teammates that would be prepared from the active account.

Why

Linked Issue

Fixes #8770

Related: #2284, #6921, #9456, #19900, #17796

Visual Proof

N/A: CLI/RPC-only change, with no renderer UI touched. Launch receipts gain an optional account field.

Testing

  • pnpm typecheck and pnpm lint pass, including the rpc params catalog, max-lines and localization checks.
  • Targeted suites: src/main/claude-accounts, src/main/rate-limits, src/main/ipc/pty*, src/cli, src/main/runtime/rpc/methods/orchestration, src/main/agent-launch and the new runtime tests.
    • The only failures are codex-fetcher-pty-settle.test.ts, which fails identically on main, and one timing-sensitive test that passes in isolation on both main and this branch.
  • Full pnpm test (on an earlier revision of this branch; after the review fixes only the targeted suites above were re-run, 0 new failures): 88,248 passed and 40 failed, in 23 files. None of the failures are in files this PR touches. Confirmed identical on main: browser-manager-viewport-ownership and codex-fetcher-pty-settle. The rest are real-binary, e2e-release and long-timeout suites on an 8 GB machine.
  • pnpm build: not run locally (8 GB machine); relying on CI.
  • New tests (mutual exclusion both ways between pinned launch and switch, remove, re-auth and usage fetch; a regression test that an unrelated launch is not delayed while a pinned launch waits; reservation release on timeout and refusal; host-terminal attribution; plus):
    • resolver: id, email, ambiguity, WSL, unknown;
    • pinned PTY registry: lifecycle, persistence, restore;
    • pinned launch preparation per platform: seed, skip-while-live, read-back adopt/reject (re-auth wins, identity mismatch), crash-marker recovery;
    • config-dir preparation: link or junction, add-only merge, no clobber of an existing dir;
    • spawn refusals (SSH, non-Claude, provenance mismatch);
    • worker-start and worktree-create threading, CLI flag and capability gating;
    • guards against switching or removing a pinned account, and against usage-fetch refresh.
  • No-regression proof: pty-runtime-claude-account-spawn.test.ts asserts that a launch without --account prepares auth with the unchanged single-argument call. It sets no config-dir variables, and produces the same env and spawn options as before.
  • Manual testing: not done against real accounts. Exercising the seed and read-back on live subscriptions risks the refresh-token family, so I kept verification to the mocked keychain and filesystem harnesses. A maintainer smoke test on macOS with two managed accounts would be very welcome:
    1. worker-start --account <inactive>;
    2. check /status in the pane;
    3. confirm the host account is unchanged;
    4. exit the pane and confirm the managed account still refreshes.

Platforms: macOS (tests). Linux and Windows paths are covered by unit tests with a mocked platform; the Windows junction path is covered only through mocked symlinkSync.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Implemented with Claude Code (Anthropic Claude Opus 5.5) under my direction. That covers design research against the existing WSL, inactive-preview and Codex per-pane precedents, the implementation, the tests and review passes. I reviewed the design and the diff.

Review

AI review summary (security and correctness passes over the full diff):

  • Cross-platform: the macOS keychain seed and read-back only run on darwin. Linux and Windows use the managed .credentials.json directly. The projects link is a junction on win32. Paths use node:path.
  • SSH/remote/local: pinned launches are host-local only. SSH, --on, runtime: hosts and WSL targets are refused before any worktree or dispatch row is created.
  • Agents/integrations: Claude only. Other agents reject --account. Agent Teams uses in-process teammates for pinned launches.
  • Performance: there is no new work on the no---account path. Pinned-dir preparation runs once per pinned launch. Crash markers are read once at startup.
  • Security:
    • No credential is logged or put in the environment; only config-dir paths are.
    • The marker file stores a hash, never the secret.
    • The legacy unscoped keychain item is never written from the pinned path.
    • Refresh is single-owner per account: pinned launch, switch, remove, re-auth and usage fetch claim atomically against each other.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Known limitations, documented as follow-ups:

  • A shell rc that exports CLAUDE_CONFIG_DIR overrides the pin.
  • CLAUDE.md, agents, commands, skills, plugins and MCP config from ~/.claude are not inherited by the pinned dir; only projects and settings.json are.
  • A pane restored after the daemon dies resumes on the active account.
  • A restored host terminal from a build before this one has no account record. It is attributed to the account selected at startup.
  • settings.json is re-copied from the host on each pinned launch.

Possible follow-ups:

  • orca terminal create --account;
  • persisting the pin for sleep/resume;
  • WSL;
  • UI pickers and a status badge;
  • a narrower Claude-only accounts accessor. worker-start and worktree create currently call getAccountsSnapshot().

This PR does overlap with open work: #22068 touches the same spawn-preflight auth call, and #22510 touches runtime-auth-sync. I kept the edits at those points minimal and am happy to rebase behind either.

Checklist

  • This PR is small and focused (focused on one feature, but not small: ~3.9k lines incl. tests, most of it the refresh-token ownership guards, which have to be complete in the first version)
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred) (lint and typecheck pass; see Testing for the pre-existing test failures; build is left to CI)

Selection and removal checked for pinned terminals before the pinned launch's
reservation existed, and a usage fetch could stage or refresh an account while a
pinned launch seeded the same Keychain item. Host mutations, usage fetches and
pinned reservations now claim an account with one synchronous check-and-set and
hold it until they finish; a pinned launch waits out a usage fetch and refuses a
switch or removal in progress.
…queue

A pinned launch that met an in-flight usage fetch waited up to 40s inside the
process-wide auth mutation queue, stalling every Claude launch and sync. The
reservation is now taken (and waited for) before entering the queue; the queued
part re-validates the account and the reservation is released on every failure.
… writes

Re-auth replaced an account's managed credentials without the host-mutation
claim, so it could overwrite the store a pinned --account Claude refreshes. It
now refuses up front while pinned terminals hold the account and claims the
account from the credential write through rollback.
…als still run on

A host Claude keeps the account it started on and refreshes it through
~/.claude after the host switches away, so pinning that account with --account
gave one single-use refresh chain two owners. Unpinned host Claude PTYs and
structured children now record the account they started on (from the prepared
auth provenance, else the host selection), restored across restarts, and a
pinned reservation refuses while any of them is still live.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — the pinned-launch mechanism, its refresh-ownership guards, and the no---account path all check out. Two rough edges inline, plus a nitpick.

Reviewed changes

Read the full 79-file diff and ran the new suites (all green): src/main/claude-accounts (33 files / 275 tests), the new CLI tests for worker-start/worktree create --account, the runtime PTY pinned-spawn suite, the runtime startup/Agent-Teams suites, and claude-fetcher-managed-account-usage.

  • Selector resolution — --account <id|email> resolves on the worker host; exact id beats a case-insensitive email, ambiguity/unknown/WSL refused, and a selector naming the active account reports isActiveOnHost and takes the normal path.
  • Pinned auth preparation — CLAUDE_CONFIG_DIR + CLAUDE_SECURESTORAGE_CONFIG_DIR point at the managed auth dir; the config-dir helper links projects, mirrors settings.json, and add-only merges .claude.json first-run/trust/oauth keys. macOS seeds the dir-scoped Keychain item and reads it back on drain behind identity + seed-hash proof.
  • Refresh ownership — a per-account registry makes pinned reservations mutually exclusive with host switch/remove/re-auth and with Orca usage fetches, tracks live pinned PTYs (persisted + reconciled against the daemon), and attributes ordinary host PTYs to the account they launched on.
  • Plumbing — CLI flag + claude.launch-account.v1 capability gate; params threaded through worker-start and worktree-create into the runtime PTY spawn; Agent Teams forced in-process for an --account leader; structured workers downgraded with reason pinned_claude_account; SSH/remote/WSL and the renderer pty:spawn lane refused.

ℹ️ Nitpicks

  • src/main/runtime/runtime-worktree-startup-claude-account.ts:25 — when request.startup is set the throw reuses '--account requires --agent claude.' even though the agent may be Claude; a distinct message (e.g. "--account cannot combine with an explicit startup command") would save a confusing debug.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment on lines +140 to +142
if (placement.claudeAccount) {
return downgraded('pinned_claude_account', vocabulary)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pinned_claude_account downgrade fires for any --account, including a selector that names the account already active on the host. The PR's resolution rules say an active-account selector runs the normal path unchanged, but this decision runs before resolution and only sees the raw selector, so a structured-default user gets a terminal worker (and the “non-active account” detail) for --account <active>. The same raw-selector assumption makes buildRuntimeAgentTeamsLaunchPlan force in-process teammates for the active account too.

Technical details
# `--account <active>` is treated as pinned in mode/Agent-Teams decisions

## Affected sites
- `src/main/agent-launch/agent-launch-mode.ts:140` — downgrades for any non-empty `placement.claudeAccount`.
- `src/main/runtime/rpc/methods/orchestration-worker-start-mode.ts:60` — passes the raw `params.account` as `claudeAccount`.
- `src/main/runtime/orca-runtime-agent-teams-launch-plan.ts:35` — forces `in-process` for any `claudeAccountId`.

## Required outcome
- Either only downgrade / force in-process when the resolved account is not the active host account (resolution already exists: `resolveClaudeLaunchAccount(...).isActiveOnHost`), or document that `--account` is selector-level terminal-only regardless of which account it names so the receipt and the Agent-Teams mode stop contradicting the resolution rules.

## Open questions for the human
- Is the active-account case worth special-casing, or is the selector-level behavior intentional and just under-documented?


/** Claims an account for an Orca-side usage fetch; null while pinned launches hold it. */
export function beginClaudeAccountUsageFetch(accountId: string): (() => void) | null {
const release = claimUnlessPinned(usageFetchesByAccountId, accountId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beginClaudeAccountUsageFetch claims only against pinned reservations (claimUnlessPinned), so a usage fetch and a host mutation (switch/remove/re-auth) are not mutually exclusive even though both touch the same managed credentials — unlike every other pair the registry guards. During a re-auth, a concurrent usage fetch can read the pre-re-auth credentials, refresh them, and write them back over the freshly written login.

Technical details
# usage fetch vs host mutation are not mutually exclusive

## Affected sites
- `src/main/claude-accounts/claude-pinned-pty-registry.ts:94` — `claimUnlessPinned` checks `countClaudePinnedAccountUsers` but not `hostMutationsByAccountId`.
- `src/main/claude-accounts/claude-account-registration.ts:132-155` — re-auth holds the host-mutation claim across its writes.
- `src/main/rate-limits/claude-managed-account-usage.ts:47,74-87` — a claimed usage fetch refreshes and persists managed credentials.

## Open questions for the human
- Is host-mutation ↔ usage-fetch exclusion intentionally out of scope (a pre-existing race), or should `beginClaudeAccountUsageFetch` also refuse while `hostMutationsByAccountId` holds the account? If out of scope, a note in the registry JSDoc would keep the “one owner per account” claim honest.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The change adds a --account selector for Claude worker-start and worktree-create launches. The selected managed account is resolved and passed through local launch paths to PTY authentication preparation. New PTY registries track pinned and ordinary host Claude sessions, persist account attribution, and coordinate account mutations and usage fetches. Pinned launches use managed config directories and scoped credentials. Startup code restores PTY tracking and reconciles eligible credential state.

Merge Risk: 🟡 Moderate · up to 87c9f

This change adds per-launch Claude account selection. After a restart in which the background session daemon fails to come up, managed accounts used by the earlier terminals can stay locked. Later --account launches, account switches, and account removals may then be refused until a successful daemon reconciliation clears the stale records. Clearing those records when startup falls back to non-persistent sessions should be resolved before merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #8770 requires CLI account selection for agent spawns, including orca terminal create --agent claude --account <id|email>. The PR implements account selection for worktree create and `worker… Add --account <id|email> support to the terminal create CLI specification and handler. Validate that the launch is Claude, check the runtime capability, forward the resolved account through terminal.create, and add automated coverage …
Docstring Coverage ❓ Inconclusive Docstring coverage is 32.18% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 87 functions across 50 files. (29 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: launching Claude agents on a specific managed account with the new --account option.
Description check ✅ Passed The description follows the required template and provides a clear user-facing summary, implementation details, rationale, linked issue, visual-proof explanation, testing results, AI disclosure, revie…
Out of Scope Changes check ✅ Passed The pinned-PTY registry, credential staging, account-resolution, usage-fetch coordination, and host-mutation guards support launching Claude on a selected non-active managed account without corrupting…
Full details: Linked Issues check

Explanation

Issue #8770 requires CLI account selection for agent spawns, including orca terminal create --agent claude --account &lt;id|email&gt;. The PR implements account selection for worktree create and worker-start, and orca account list already exists. However, src/cli/specs/core.ts still defines terminal create without account or agent, and src/cli/handlers/terminal.ts does not read or forward an account. The runtime account plumbing therefore cannot be used by the documented terminal spawn path.

Resolution

Add --account &lt;id|email&gt; support to the terminal create CLI specification and handler. Validate that the launch is Claude, check the runtime capability, forward the resolved account through terminal.create, and add automated coverage for success, unsupported runtimes, and non-Claude launches.

Full details: Docstring Coverage

Explanation

Docstring coverage is 32.18% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 87 functions across 50 files. (29 skipped: 29 over the file limit.)

  • Fix all pre-merge checks with AI

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 9fe5b56a-d9f4-443e-8868-8bc3050871dd

📥 Commits

Reviewing files that changed from the base of the PR and between 85642d0 and 87c9fe4.

📒 Files selected for processing (79)
  • src/cli/handlers/claude-launch-account-flag.ts
  • src/cli/handlers/orchestration-worker-account-cli.test.ts
  • src/cli/handlers/orchestration/worker-launch-handler.ts
  • src/cli/handlers/worktree.ts
  • src/cli/index-worktree-create-claude-account.test.ts
  • src/cli/specs/core.ts
  • src/cli/specs/orchestration-worker-specs.ts
  • src/main/agent-launch/agent-launch-mode.ts
  • src/main/claude-accounts/claude-account-host-mutation.ts
  • src/main/claude-accounts/claude-account-registration.ts
  • src/main/claude-accounts/claude-account-selection.ts
  • src/main/claude-accounts/claude-account-service-account-selection.test.ts
  • src/main/claude-accounts/claude-account-service-reauth-pinned.test.ts
  • src/main/claude-accounts/claude-host-pty-accounts.test.ts
  • src/main/claude-accounts/claude-host-pty-accounts.ts
  • src/main/claude-accounts/claude-launch-account.test.ts
  • src/main/claude-accounts/claude-launch-account.ts
  • src/main/claude-accounts/claude-pinned-config-dir.test.ts
  • src/main/claude-accounts/claude-pinned-config-dir.ts
  • src/main/claude-accounts/claude-pinned-credentials.ts
  • src/main/claude-accounts/claude-pinned-pty-registry.test.ts
  • src/main/claude-accounts/claude-pinned-pty-registry.ts
  • src/main/claude-accounts/environment.ts
  • src/main/claude-accounts/live-pty-gate.test.ts
  • src/main/claude-accounts/live-pty-gate.ts
  • src/main/claude-accounts/runtime-auth-service-pinned-launch.test.ts
  • src/main/claude-accounts/runtime-auth-service.ts
  • src/main/claude-accounts/runtime-auth/runtime-auth-pinned-launch.ts
  • src/main/claude-accounts/runtime-auth/runtime-auth-readback.ts
  • src/main/claude-accounts/runtime-auth/runtime-auth-sync.ts
  • src/main/claude-accounts/runtime-auth/runtime-auth-types.ts
  • src/main/daemon/daemon-provider-init.ts
  • src/main/ipc/pty-runtime-claude-account-spawn.test.ts
  • src/main/ipc/pty-spawn-env-terminal-basics.test.ts
  • src/main/ipc/pty/host-env/types.ts
  • src/main/ipc/pty/ipc/spawn-commit.ts
  • src/main/ipc/pty/runtime/spawn-claude-account.ts
  • src/main/ipc/pty/runtime/spawn-commit.ts
  • src/main/ipc/pty/runtime/spawn-preflight.ts
  • src/main/ipc/pty/runtime/spawn-state.ts
  • src/main/ipc/pty/runtime/spawn.ts
  • src/main/rate-limits/claude-fetcher-managed-account-usage.test.ts
  • src/main/rate-limits/claude-managed-account-credentials.ts
  • src/main/rate-limits/claude-managed-account-usage.ts
  • src/main/runtime/orca-runtime-agent-teams-launch-plan.test.ts
  • src/main/runtime/orca-runtime-agent-teams-launch-plan.ts
  • src/main/runtime/orca-runtime-create-managed-worktree.ts
  • src/main/runtime/orca-runtime-create-terminal-desktop.ts
  • src/main/runtime/orca-runtime-create-terminal.ts
  • src/main/runtime/rpc/methods/orchestration-worker-start-mode.ts
  • src/main/runtime/rpc/methods/orchestration/worker/local-worker-start.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-claude-account-placement.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-claude-account.test.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-launch-preferences.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-start-agent-placement.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-start-validation.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-topology.ts
  • src/main/runtime/rpc/methods/orchestration/worker/worker-worktree-creation.ts
  • src/main/runtime/rpc/methods/orchestration/worker/workers.ts
  • src/main/runtime/rpc/methods/worktree-create-args.ts
  • src/main/runtime/rpc/methods/worktree-create-claude-account-args.test.ts
  • src/main/runtime/runtime-folder-worktree-create.ts
  • src/main/runtime/runtime-local-worktree-terminal-startup.test.ts
  • src/main/runtime/runtime-local-worktree-terminal-startup.ts
  • src/main/runtime/runtime-managed-worktree-create-types.ts
  • src/main/runtime/runtime-pty-controller-contract.ts
  • src/main/runtime/runtime-terminal-contracts.ts
  • src/main/runtime/runtime-worktree-startup-claude-account.test.ts
  • src/main/runtime/runtime-worktree-startup-claude-account.ts
  • src/main/startup/main-process-account-services.ts
  • src/main/startup/main-process-ready-foundation.ts
  • src/main/startup/main-process-runtime-launch.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/main/startup/main-window-core-services.ts
  • src/main/window/attach-main-window-services.ts
  • src/shared/agent-launch-intent.ts
  • src/shared/protocol-version.ts
  • src/shared/rpc-contract/orchestration-worker-start-params.ts
  • src/shared/rpc-contract/worktree-create-params.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +271 to +291
// Why: `--account` must not pin an account a surviving host Claude still refreshes.
setClaudeActiveHostAccountResolver(() =>
getSelectedClaudeAccountIdForTarget(store.getSettings(), { runtime: 'host' })
)
const hostClaudePtyAccountsPath = join(app.getPath('userData'), 'claude-host-pane-accounts.json')
attachClaudeHostPtyAccountPersistence(
createClaudeHostPtyAccountFilePersistence(hostClaudePtyAccountsPath)
)
seedHostClaudePtyAccounts(
persistedClaudePtyIds,
readClaudeHostPtyAccountsFile(hostClaudePtyAccountsPath)
)
// Why: same restart hazard for `--account` PTYs, whose accounts the global gate never covers.
const pinnedClaudePtyRegistryPath = join(
app.getPath('userData'),
'claude-pinned-pane-accounts.json'
)
seedPinnedClaudePtysFromPersistence(readClaudePinnedPtyRegistryFile(pinnedClaudePtyRegistryPath))
attachClaudePinnedPtyPersistence(
createClaudePinnedPtyFilePersistence(pinnedClaudePtyRegistryPath)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Who drives seed confirmation, and does a non-daemon startup ever reach it?
rg -n -C5 'reconcileSeededClaudeLivePtys|confirmSeededPinnedClaudePtys|confirmSeededClaudeLivePtys' src/main
rg -n -C5 'initDaemonPtyProvider|daemonProviderInit|LocalPtyProvider\(' src/main/daemon src/main/startup

Repository: stablyai/orca

Length of output: 41933


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- daemon provider initialization ---'
sed -n '45,230p' src/main/daemon/daemon-provider-init.ts
printf '%s\n' '--- startup PTY provider wiring ---'
sed -n '95,165p' src/main/startup/main-process-pty-startup.ts
printf '%s\n' '--- pinned registry seed/count and reservation behavior ---'
sed -n '150,280p' src/main/claude-accounts/claude-pinned-pty-registry.ts
printf '%s\n' '--- host account seed/count and reservation behavior ---'
rg -n -C8 'seedHostClaudePtyAccounts|countHostClaudePtysForAccount|host-sessions|attachClaudeHostPtyPersistence' src/main/claude-accounts

Repository: stablyai/orca

Length of output: 34888


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- startup service orchestration ---'
rg -n -C12 'function startFirstWindowStartupServices|startFirstWindowStartupServices|startDaemonPtyProvider|LocalPtyProvider|persistent' src/main/startup src/main/ipc src/main/daemon
printf '%s\n' '--- daemon startup failure and fallback handling ---'
rg -n -C12 'daemon-pty-provider|initDaemonPtyProvider|setLocalPtyProvider|DegradedDaemonPtyProvider|fallback' src/main/startup src/main/daemon

Repository: stablyai/orca

Length of output: 45544


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- defining files ---'
rg -l 'function startFirstWindowStartupServices|export function startFirstWindowStartupServices|const startFirstWindowStartupServices' src/main
printf '%s\n' '--- startup service references ---'
rg -n 'startFirstWindowStartupServices|startDaemonPtyProvider' src/main/startup --glob '*.ts' --glob '!*.test.ts'
printf '%s\n' '--- persistent-provider state references ---'
rg -n -C8 'daemonOwnsFreshPersistentPtys|LocalPtyProvider|setLocalPtyProvider' src/main/startup src/main/daemon --glob '*.ts' --glob '!*.test.ts'

Repository: stablyai/orca

Length of output: 37803


🏁 Script executed:

#!/bin/bash
set -e
cat -n src/main/startup/first-window-startup-services.ts | sed -n '1,170p'

Repository: stablyai/orca

Length of output: 4795


Clear persisted Claude PTY seeds after a non-persistent startup.

initializeReadyFoundation seeds both registries from persistence. reconcileSeededClaudeLivePtys runs only after daemon initialization completes. If daemon initialization fails before reconciliation, or if session listing fails, the seeds remain.

Those seeds can keep accounts blocked after the previous local PTYs have exited. The host registry counts seeded entries, and reserveClaudePinnedAccount then rejects later --account launches with the host-sessions conflict.

When startup selects a non-persistent provider, clear both registries through their existing confirmation functions with an empty alive-session set. Keep the seeds when a persistent daemon may still own sessions or when daemon listing fails.

@Ethan-Rivas

Copy link
Copy Markdown

Thanks for this, @fardenghi. The per-launch primitive is exactly what per-project accounts needed.

I built the GUI layer on top of it: each project can remember its Claude account (Settings → Repository), with an optional "which account?" prompt on launch, and every desktop launch path honors it (new tab, ⌘⌥T, New Workspace, resume/restore, paired hosts). It reuses your pinned path for both spawn lanes, including the renderer pty:spawn lane, through helpers shared with spawn-claude-account.ts, so there's no new credential mechanism. It's up as #23228 (stacked on this one), with the feature request in #23227. It depends on this PR landing, so I'm happy to adapt to any changes here.

A few things I ran into while building and testing on macOS with two real subscriptions:

  1. Conflicts with main: this branch now conflicts with main (in my local trial, agent-launch-mode.ts and orchestration-worker-specs.ts, both additive). Happy to help test a rebase.
  2. The runtime commit path: in runtime/spawn-commit.ts the pinned PTY is registered after several post-spawn steps. If one of them throws, a live pinned PTY is left unregistered while its reservation is released. I moved the registration before those steps in my branch (both lanes). Worth considering here too.
  3. Account "in use" tracks the PTY: if claude exits but its shell stays open, the account stays "in use" until the shell closes. Probably fine, just noting it.

Thanks again, and let me know if you'd like the layer structured differently.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Select the Claude account for headless/agent spawns via CLI (--account / orca account list)

2 participants