Skip to content

feat(accounts): keep a Claude or Codex agent on its account - #28684

Open
QichangZheng wants to merge 35 commits into
stablyai:mainfrom
QichangZheng:feat/agent-account-binding
Open

QichangZheng wants to merge 35 commits into
stablyai:mainfrom
QichangZheng:feat/agent-account-binding

Conversation

@QichangZheng

@QichangZheng QichangZheng commented Oct 14, 2026 •

Copy link
Copy Markdown

ELI5

Each Claude or Codex agent tab now shows which account it runs on. Right-click the tab and choose Keep on <account>: switching the selected account no longer moves that agent, and when Orca restores, wakes or resumes it, it comes back on the same account. Agents you don't keep work exactly as before.

This is the first of three small steps toward running different agents on different accounts at the same time (the other two are under Why).

What Changed

What you see

  • Tab hover card. Each Claude or Codex agent in the tab gets a line naming its account and what that means for its next start: Kept on this account, Follows the selected account, or Kept on this account from its next start (a Claude agent kept after it started; see Review notes). A split tab gets one line per agent pane.
  • Tab right-click menu. A Keep on <account> checkbox per agent pane. It is disabled, with the reason, until the agent reports its session, and for a Codex agent started while CODEX_HOME was set.
  • "Account switched" prompt (Codex). Keep old account now keeps the pane on its account; it used to forget the pane's launch record. Escape and a backdrop click dismiss the prompt as before.
  • Account switchers (Claude and Codex). Each account row shows "N terminal agents". The Claude footnote now says that agents kept on an account keep it too.
  • Removing an account releases the agents kept on it (they follow the selected account from their next start). The confirmation says so, with the number of open terminal agents affected.
  • Docs. A "Keep an agent on one account" section on the Codex hot-swap page and one paragraph on the Claude page.

What a kept agent does. It stays on its account through global account switches, Orca restarts, sleep and wake, cold restore, and Session History resume. Un-keeping is the same checkbox.

Agents nobody kept. Both PTY spawn paths make the same calls with the same arguments as before, and no new file, timer, poll or process appears until someone presses Keep. The one always-on addition is in-memory: the account is worked out when an agent's status row changes (a provider with no saved accounts publishes nothing).

Mechanism

  1. Agent Status carries the account. Status rows get an optional runAccount facet (provider, lane, account, follow or kept) written only by the host. The renderer reads it from agentStatusByPaneKey and owns only labels and copy. Paired clients, mobile and worktree ps project rows through pickParsedAgentStatusPayload, which does not copy the new field, so nothing a remote peer receives changes. A facet-only restatement of a row leaves its clocks, observation and state alone and is forwarded to the renderer only (no title, bell, telemetry or plugin events).
  2. Codex reuses the launch record. A kept flag on the existing per-terminal record in codex-pane-account-registry; the stale-pane sweep and registry trimming skip kept records. A kept conversation resumes in its account's home through the existing restart-resume materialization and the existing ownership check.
  3. Claude gets one pointer file per account, not per pane. An agent started kept reads claude-profile-pointers/account-<id> instead of the selection pointer. Orca never deletes these files, so closing or moving tabs cannot unpin an agent. Removing an account first points its file at an empty placeholder folder, so a claude typed again in that terminal reaches Claude's own sign-in, never another account's login. The claude shell function and the selection pointer's writer are unchanged.
  4. Kept conversations are remembered by session id in a small index (agent-kept-conversations.json): explicit keeps only, no cap, no expiry, and the file does not exist until the first keep. A keep follows a session-id change inside the same Claude process (/clear, /resume). An unreadable file is reported as unavailable rather than empty, a file written by a newer Orca is never rewritten, and a corrupt one is set aside, not deleted.
  5. One resolver at both spawn points. A synchronous resolver decides, for every Claude or Codex resume, whether the conversation was kept; the window spawn and the runtime controller call it at the same place.
  6. It fails closed. A kept start Orca cannot honour refuses, with a message naming the account and the fix, instead of running another account: unreadable index, an account that is no longer saved, a Codex home that is unavailable or untrusted, a Codex conversation kept on System default while CODEX_HOME is set (it would need a second copy of the login), a kept Codex transcript that cannot be found, and a lane mismatch. Refusing a kept resume never traps the agent: un-keeping needs no terminal.

Not in this PR: WSL panes (no account shown, Keep not offered, "Keep old account" as today), SSH relay panes and remote runtimes, native chat sessions (chats publish no account and are not counted), a default account per workspace, an --account option on CLI/RPC launches, placing new agents by usage, and moving idle agents.

Why

Orca has one selected Claude account and one selected Codex account per runtime. Every new agent starts on it, and switching moves the next start of every agent at once. People with more than one subscription want some agents to stay where they are while others move.

  • Reuse over a new store. The Codex launch record, the restart-resume path, the ownership check and the Agent Status store already exist; this adds one flag, one small index of explicit keeps, and one field on the status row, rather than a parallel per-pane binding store.
  • Alternatives I did not take. Switching everyone at once at a usage line (no way to keep one agent where it is); a pointer file per pane (it fails open if the file is ever missing: the agent would silently run System default); copying a login per agent (refresh tokens rotate as one chain, so copies invalidate each other). Agents on one account share that account's folder or home, as today.

Where this goes (not part of this PR). Step 2 is opt-in and off by default: an agent start that is not kept picks the account on its lane with the longest runway before a configurable line (default 90%), using only readings Orca trusts; an account at its line takes no new starts, and an account Orca cannot read is never a target. Step 3 is also opt-in: when an account reaches its line, agents on it that have finished their turn and sat idle (cache cold) get a one-click "Continue on <account>", with a separate setting to do it automatically; an agent that is generating, waiting for an answer or permission, or kept is never moved. The pieces left in this PR for those steps: the kept mode and the account facet, one resolver both spawn paths already call, the per-account Claude pointer, and the account-parameterized Codex home resolution for resume.

Linked Issue

Fixes #28683

Related: #9496 (this is its per-session half; a per-workspace default can build on it) and #20512 (this is the first step toward agent-level account scheduling).

Visual Proof

Before is the base commit's build, after is this branch, same scenario, light and dark. Both come from the repo's Playwright + Electron e2e harness under Xvfb, with placeholder accounts (alice@example.com, bob@example.com) and no real login. No real claude or codex ran: agent status rows were driven by posting hook events to the app's local hook endpoint, and the usage percentages in the status bar are injected demo values. "Sign in to see usage" under alice@example.com is only the placeholder account having no login. In every shot the selected account is alice@example.com, except the switchers, where it has moved to bob@example.com after two agents started on alice.

Shows Before After
Tab hover card on a Claude agent: before has no account line; after reads "alice@example.com · Follows the selected account" (light) before light after light
Tab hover card on a Claude agent: before has no account line; after reads "alice@example.com · Follows the selected account" (dark) before dark after dark
The same card after Keep: "Kept on this account from its next start" (light) — after light
The same card after Keep: "Kept on this account from its next start" (dark) — after dark
Tab right-click menu: before has no Keep item; after has an unchecked "Keep on alice@example.com" (light) before light after light
Tab right-click menu: before has no Keep item; after has an unchecked "Keep on alice@example.com" (dark) before dark after dark
The same menu with Keep checked (light) — after light
The same menu with Keep checked (dark) — after dark
Claude account switcher: "2 terminal agents" on alice, "1 terminal agent" on bob, plus the new footnote (light) before light after light
Claude account switcher: "2 terminal agents" on alice, "1 terminal agent" on bob, plus the new footnote (dark) before dark after dark
Codex account switcher, same counts (light) before light after light
Codex account switcher, same counts (dark) before dark after dark

Not captured as images (covered by unit tests instead): the account-removal confirmation line, the Codex "Account switched" prompt with Keep old account, and a split tab's one-line-per-pane hover card.

Testing

How a reviewer can try it by hand:

  1. In Settings > Accounts, add two Claude (or Codex) accounts and select the first.
  2. Start an agent in a terminal tab and hover the tab: its account is on the card. Right-click the tab and check Keep on <account>.
  3. Switch the selected account from the status bar. A plain agent follows it as before; the kept one stays. Open the account switcher to see the per-account counts.
  4. Restart Orca, or let the agent sleep and wake it, or resume it from Session History: the kept agent comes back on its account. Uncheck the item and it follows the selection again.

What I ran, on Linux only (x64, Node 24, headless Electron under Xvfb). I did not test on macOS or Windows, and I did not click through the app by hand: the UI was driven through the Playwright e2e harness.

  • Typecheck: tc:node, tc:cli, tc:web pass.

  • Lint: every step of pnpm lint passes except one oxlint finding in a file this PR does not touch: src/main/orcad/orcad-entry.ts is 301 lines against a limit of 300, and the file is identical at the base commit. The chain stops at its first failure, so I ran the 22 steps after oxlint one by one and all pass (including the max-lines and layout-writer ratchets, the localization catalogs, extraction and coverage checks, and the design-system and changed-code quality gate). oxfmt --check is clean on the 123 changed files.

  • Unit tests: the 34 test files this PR adds or changes plus 6 existing suites that must keep passing unchanged (PTY launch parity and race parity, Codex home routing, daemon spawn auth, the Claude profile router, the shared-state-survives-a-failed-read test) are 40 files, 466 tests, all passing. A wider run over src/main (agent-hooks, ipc, startup, claude-accounts, codex, codex-accounts, agent-accounts), src/shared, src/preload and the renderer's tab-bar, status-bar, settings and store folders passed 30,030 tests and failed 6. Five of those fail the same way on the base commit and depend on the machine (running as root, a codex binary on PATH); the sixth is a 30 s timeout in windows-transient-lock-removal.test.ts under heavy load, and that file passes on its own on this branch (6/6) and on the base.

  • e2e: the two new specs, agent-account-keep-claude.spec.ts and agent-account-keep-codex.spec.ts, and the existing codex-account-switch-dismiss.spec.ts pass (3/3). The Claude spec puts a stand-in claude on PATH that prints which account folder and pointer file a launch reads, so the real shell function and pointer are exercised without starting Claude; it is skipped on Windows.

  • Not run: a production pnpm build (only an e2e-mode electron-vite build), the other e2e specs, and the PowerShell and fish claude functions beyond their existing unit tests.

  • Not observed: a Claude or Codex resume across two real subscriptions. No test starts a real agent, and a kept agent only ever resumes on the account it ran on.

  • I manually tested these changes locally (driven through the e2e harness on Linux, not by hand; macOS and Windows not tested)

  • Automated tests added/updated, or explained why not below

AI Disclosure

Claude Opus 5.5 (Anthropic), used through Claude Code, took part in the design, the implementation and the review of this change. I reviewed the result.

Review

Where I would most like a reviewer's eye:

  1. Facet-only republish. It restates a status row with a new runAccount and is forwarded to the renderer only, so plugin listeners, the session recorder and paired clients see no change. The window listener must not treat it as a status event (a Codex pane waiting for approval would otherwise get a forced "action required" title and a bell), so the republish is flagged, and a test covers it.
  2. Removing an account releases its kept agents to the selection, with a sentence in the confirmation, instead of refusing them from then on.
  3. A Claude agent kept after it started keeps its conversation: every Orca-made start of it (resume, wake, restore) runs on the kept account. A claude typed by hand into that already-open terminal still follows the selection until the next Orca-made start, because that terminal's environment was fixed when it started; the hover card says "from its next start". Codex has no such gap (its home is fixed at spawn).
  4. "Keep old account" now writes kept instead of deleting the record. For the prompt this is equivalent; the difference is that the keep now survives a restore.
  5. Size. About 3,000 added non-test lines across 83 files, plus about 4,900 lines of tests and docs, in 25 commits ordered bottom-up (Codex flag, status facet, kept index, resume, Claude pointer, spawn paths, UI, docs, e2e), so they can be read in order.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security. No credential is copied or moved; agents on one account share its folder or home. Pointer files and the kept index are written atomically with mode 0600 (the pointer folder 0700). A kept start that cannot be honoured refuses rather than falling back to another account.
  • Cross-platform. Keep is host-lane only: not offered for WSL panes, SSH relay panes or remote runtimes. The claude shell functions (POSIX, fish, PowerShell) are unchanged. The new Claude e2e uses a POSIX probe and is skipped on Windows.
  • Remote SSH / mobile / paired clients. Rows from remote hosts never carry the facet, and the paired-client projection does not copy it. The web client's preload shim stubs the new keepStalePanes call; web clients never see the facet, so they offer no Keep.
  • Backwards compatibility. No migration. Existing panes and conversations have no entry and behave as today. The new fields are optional, and an older Orca ignores them.
  • Performance. No new timers, polls or child processes; the facet is computed in memory when a row changes, and the status-bar count is a selector over rows already in the store. The kept index and the pointer folder are created at the first Keep. On startup, if the index exists, keeps whose transcript has been definitively gone for over a week are dropped.

Checklist

  • This PR is small and focused
  • 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)

QichangZheng and others added 25 commits October 14, 2026 12:39
A Codex pane's launch record gains an optional `kept` flag. The stale-pane
sweep skips kept records, so a kept pane is never offered the "Account
switched" prompt, and registry trimming never drops a kept record.
`setCodexPaneKept` refuses against a registry it could not read instead of
queuing a record it never saw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Keep old account" in the Account switched prompt used to delete the pane's
launch record. It now marks the pane kept, so the answer survives restarts
and the record still says which account the pane runs on. Panes whose home
cannot stay on an account (the shared CODEX_HOME mirror, WSL panes, records
from before route provenance) keep today's behaviour: the record is dropped.

The channel becomes codexAccounts:keepStalePanes and also carries the pane
key, so the host can keep the pane's conversation as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent Status rows gain an optional runAccount facet: which Claude or Codex
account a local terminal agent runs on, and whether a person kept it there.
The hook server is the only writer. republishRunAccount restates a row
with a new facet and nothing else: its clocks, observation and state stay
as they were, it is persisted with the row, and only the renderer is told,
so plugin listeners, the session recorder and paired clients see no change.

Later rows of the same agent keep the facet; a different agent in the pane
drops it. Hydration validates it and keeps it only for local rows. The
paired-client projections (pickParsedAgentStatusPayload) never copy it.
In the renderer a host row's facet is copied as is; a renderer-local row
(an OSC repaint) keeps the facet the host last sent for that agent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host-side producer writes the runAccount facet onto local Claude and
Codex terminal rows. It never guesses:

- Codex reads the pane's existing launch record (account, kept flag,
  whether its home can be kept). WSL lanes publish nothing.
- Claude dates a session to the account it started on: the pane's own
  pointer for panes started on one, otherwise the selection at the moment
  this runtime saw the session start. A session that was already running
  when it was first seen keeps the facet an earlier runtime recorded, or
  gets none. The statusline folder each post reports then corrects it, and
  a folder no saved account owns withdraws the facet.

Both spawn-commit paths note the pane's lane and whether it was a fresh
spawn. Statusline posts are forwarded to the producer separately from the
usage feed, alias-resolved, whether or not they carry usage. Providers
with no saved accounts get no facet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the only new store this feature needs: a small index of conversations
a person kept on an account, keyed by provider and session id, so a kept
agent keeps its account after its tab closes and whenever Orca starts it
again. It holds explicit keeps only, has no cap and no expiry, and the file
does not exist until the first keep. An unreadable file is reported as
unavailable rather than empty, queued changes wait until it can be read,
a corrupt file is set aside, and a file from another version is left alone.

Keeps follow the agent: a new session in a kept Codex pane is kept too, and
a new Claude session inherits its predecessor's keep only when it provably
runs on the kept account. A kept start that Claude reports under a new id is
adopted the same way. Removing an account releases what was kept on it, so
those agents follow the selection again. A startup pass drops keeps whose
transcript has been definitively gone for over a week.

"Keep old account" now keeps the pane's current conversation as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Codex resume can now be told which account a conversation is kept on.
That account's home is resolved before any ranking, with no fallback:

- a managed account must still be saved, on this host, and its home must
  pass the existing ownership check; an unreadable home is "temporarily
  unavailable", an untrusted one is refused, and the selection is never
  cleared because the kept account is not the selection;
- System default resumes in the system home, and is refused while a custom
  CODEX_HOME is set rather than using the mirror, which would be a second
  copy of the login;
- the rollout is moved into the kept home the same way Restart does;
- a kept conversation whose transcript cannot be found is refused instead
  of starting fresh on the selection.

Session History resumes of a kept conversation retarget to its account
instead of the selection, moving the rollout there from a listed home when
needed. Forks are new conversations and still follow the selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Claude agent started kept on an account gets its own which-account file,
claude-profile-pointers/account-<id>, instead of the selection's. It is
keyed by account, never by pane, so moving or closing tabs cannot unpin an
agent, and only people who have kept an agent ever get one.

Orca never deletes these files: a missing file reads as System default. Before
an account's folder is removed, its file is pointed at an empty placeholder
folder, so a `claude` typed again in such a pane reaches Claude's own sign-in
there and never another account's login; a failed removal points it back.
Whenever the selection file is written, existing account files follow
their account's route (its own folder, or System default while that covers
it).

Launches on a kept account reuse the selection's preparation: the same
first-setup wait, capped refresh and refusals, with the account's file
written before the agent starts. The env never falls back to the
selection's file. Follow panes and the shell functions are unchanged.

The credential persistence ratchet names the new file writer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One synchronous resolver decides, for every Claude or Codex resume, whether
the conversation was kept on an account. Both the window spawn and the
runtime controller call it at the same point, so the two cannot drift:

- nothing kept (and every relay pane, other agent or fresh start): the
  spawn takes exactly the calls it took before, with the same arguments;
- kept: a Claude pane gets the account's own pointer and launch
  preparation, a Codex resume is prepared in the kept account's home even
  when the request was a restart to the selection, and the new pane's
  launch record is kept too;
- a keep that cannot be honoured (unreadable index, another lane, an
  account that is no longer saved) refuses before anything starts.

Both commit paths tell the run-account producer which account pointer a
Claude pane started with and which conversation it resumed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tab hover card now names the account each Claude or Codex agent in the
tab runs on, with one line per agent pane in split tabs, and what that means
for its next start: kept on this account, kept from its next start (a Claude
agent kept after it started; a `claude` typed by hand in that terminal still
follows the selection), or following the selected account.

The tab's right-click menu offers "Keep on <account>" per agent pane. It is
disabled, with the reason, until the agent reports its session or while
CODEX_HOME is set. Releasing a Codex pane re-checks it against the selection,
so a pane left behind gets the usual Account switched prompt.

Both read the host-published facet; the renderer keeps only labels and copy.
The keep goes through a new agentAccounts:setKept channel. Web clients never
see the facet, so they offer nothing and need no shim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Claude and Codex account switchers show "N terminal agents" on each of
this host's account rows, counted from the host-published account on Agent
Status rows whose tab is open. Chats publish no account, so they are not
counted, and rows for another runtime's or a WSL lane's accounts show no
count. The Claude switcher's footnote now says that agents kept on an
account keep it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removing a Claude or Codex account releases the conversations kept on it,
so those agents follow the selected account from their next start. The
removal dialog now says so, with the number of this host's open terminal
agents kept on that account, whenever there is at least one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the account line in the tab hover card, Keep on in the tab menu,
"Keep old account", what a kept agent does through restarts, wakes and
Session History resumes, the switcher counts, account removal, and where
Keep is not offered. The Claude page explains the one difference: keeping
an agent after it started applies from its next start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A kept Codex pane is not reported stale when the selection moves, while an
unkept pane still is. A kept Claude conversation resumes on its account's
own folder and pointer while another account is selected, and a plain
terminal keeps following the selection. A probe standing in for `claude`
prints which folder and pointer a launch reads; it starts nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`tabsByWorktree: state.tabsByWorktree ?? EMPTY_TABS` counts as a renderer
layout write. The store always holds the map, so pass it through as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… before

Escape and a backdrop click shared the button's handler, so a stray key aimed
at the terminal kept the conversation on the old account for good. They now
dismiss the prompt and forget the pane's launch record, as before this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lemetry

The window listener treated the restated row as a status event, so a Codex
pane waiting for approval got a forced "action required" title with a bell:
an unread badge and an OS notification whenever a keep changed or the first
account was added. The republish is now flagged and only forwarded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A selection write rewrote every per-account pointer with no guard, so one
  file Orca could not rewrite (a directory, a read-only file) failed launches
  of agents nobody kept. Each rewrite is now caught and logged.
- A kept launch during an account's removal skipped the selection and re-ran
  setup, recreating the folder being deleted. A retiring account now refuses
  kept launches, and its terminal env names only the pointer.
- A removed account's pointer named one shared, writable placeholder folder,
  so a re-typed `claude` offered a sign-in inside Orca's data. It now holds a
  value that names no folder, which the `claude` function already refuses to
  run, and the removal dialog says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Statusline folders compare through normalizeRuntimePathForComparison, the
  key the Codex resume code already uses, instead of a second normalizer.
- The kept-conversations index and the Codex pane registry share one retry
  backoff instead of two copies of the same timer.
- Design walk-throughs in doc comments are cut to one line of why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le chain

- /clear or /resume after a global switch gave the kept Claude conversation a
  new session id that was dated to the new selection, so it lost its keep and
  later resumed on the wrong account. A session change inside the same Claude
  process (same hook process identity) now inherits the process's account,
  and a statusline post that proves the folder re-runs the carry. A carried
  keep the statusline proves runs elsewhere is dropped.
- Sessions that carry a keep on now record which keep they came from, so
  un-keeping any session of an agent releases all of them instead of leaving
  earlier ones silently kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused kept resume never gets a terminal, and releasing a Codex keep
required one, so the refusal's own advice ("un-keep it") failed and the
conversation stayed refused for good. A release now needs no terminal: it
releases the conversation from the row, clears the pane flag only if a
launch record exists, and withdraws a facet nothing observes any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… resume

A list of kept agents written by a newer Orca made this build refuse every
Claude and Codex resume, kept or not, with "try again", which never helped.
That file now reads as nothing kept and is never rewritten, and keeping from
the tab menu says why it cannot. A file that cannot be read at all still
refuses, but the message names the file and how to get past it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…prompt

The "Account switched" overlay stayed up after the pane was kept from the tab
menu, and its Restart then came back on the kept account, not the new one the
overlay named. A successful keep now dismisses the pane's prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Passing the store's tabs through unchanged (for the layout-writer ratchet)
handed undefined to the tab index in stores without tabs, which threw. The
counter now reads a missing map as no open tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oved account does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f570fd00-f1d8-4749-9ecd-e1cca5265a79
📥 Commits

Reviewing files that changed from the base of the PR and between 91c602e and c6fb488.

📒 Files selected for processing: 27
  • src/main/ai-vault/ai-vault-local-scan-progress.ts
  • src/main/ai-vault/ai-vault-scan-coordinator.test.ts
  • src/main/ai-vault/ai-vault-scan-progress-flow.test.ts
  • src/main/ai-vault/session-scanner-discovery.ts
  • src/main/ai-vault/session-scanner-scope-walk.test.ts
  • src/main/ai-vault/session-scanner-zcode-sources.ts
  • src/main/ai-vault/session-scanner.ts
  • src/main/ai-vault/session-scope-cwd.ts
  • src/main/antigravity/antigravity-acp-runtime-probe.ts
  • src/main/claude-accounts/claude-profile-account-pointers.test.ts
  • src/main/ipc/pty-claude-kept-launch.test.ts
  • src/main/ipc/pty/runtime/spawn-preflight.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-mutation-context.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-unfinished-work.ts
  • src/main/runtime/claude-structured-idle-releases-unknown.test.ts
  • src/main/runtime/session-tabs/structured-session-create-support.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/renderer/src/components/right-sidebar/AiVaultScanProgressStrip.test.tsx
  • src/renderer/src/components/right-sidebar/AiVaultScanProgressStrip.tsx
  • src/renderer/src/components/right-sidebar/ai-vault-session-refresh.ts
  • src/renderer/src/components/settings/NativeChatProvidersSetting.tsx
  • src/renderer/src/components/tab-bar/SortableTab.tsx
  • src/renderer/src/components/tab-bar/SortableTabContextMenu.tsx
  • src/renderer/src/components/tab-bar/TerminalTabHoverCard.tsx
  • src/renderer/src/components/terminal-pane/TerminalPaneRuntimePortals.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/lib/agent-account-restart-startup.ts
🚧 Files skipped from review as they are similar to previous changes: 1
  • src/renderer/src/i18n/locales/en.json

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

📝 Walkthrough

This change adds per-agent account retention for Claude and Codex. It persists kept conversations and Codex pane state, publishes account facets through host and renderer status paths, routes retained sessions to their saved accounts, and adds refusal handling when retention cannot be honored. New IPC, preload, menu, hover-card, switcher, removal-dialog, documentation, unit, integration, and end-to-end coverage supports the feature.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to c6fb4

Two bounded account-retention issues remain: releasing a kept account may report success in read-only mode, and an unavailable kept-session index may prevent some vault resumes. Confirm or accept these risks before merging.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Out of Scope Changes check Warning The feature changes are mostly within [#28683], but the PR also includes unrelated formatting-only changes in files such as src/main/ai-vault/ai-vault-local-scan-progress.ts, `src/main/antigravity/a… Revert the unrelated formatting-only changes outside the account-keeping implementation, tests, and documentation. Keep only formatting changes required for files that implement or test [#28683].
Docstring Coverage Warning Docstring coverage is 39.31% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 173 functions across 117 files. (1 skippe… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the primary change: keeping Claude or Codex agents on their selected accounts.
Description check Passed The description is complete and directly addresses the template. It includes the user-facing change, mechanism, rationale, linked issue, visual proof, testing results and limitations, AI disclosure, s…
Linked Issues check Passed The PR satisfies the coding requirements in [#28683]. It adds visible account facets, per-agent Keep controls, persistence through account switches, restart, wake, cold restore, and Session History re…
Full details: Description check

Explanation

The description is complete and directly addresses the template. It includes the user-facing change, mechanism, rationale, linked issue, visual proof, testing results and limitations, AI disclosure, scope boundaries, compatibility notes, and checklist status.

Full details: Linked Issues check

Explanation

The PR satisfies the coding requirements in [#28683]. It adds visible account facets, per-agent Keep controls, persistence through account switches, restart, wake, cold restore, and Session History resume. It preserves selected-account behavior for agents without Keep, adds account counts, and refuses unsafe kept starts instead of falling back. The reported unit and end-to-end tests cover these paths.

Full details: Out of Scope Changes check

Explanation

The feature changes are mostly within [#28683], but the PR also includes unrelated formatting-only changes in files such as src/main/ai-vault/ai-vault-local-scan-progress.ts, src/main/antigravity/antigravity-acp-runtime-probe.ts, src/main/native-chat/agent-session-wire/structured-agent-session-mutation-context.ts, and src/main/runtime/session-tabs/structured-session-create-support.ts. These files do not support per-agent account keeping. The summary identifies no functional connection to [#28683].

Resolution

Revert the unrelated formatting-only changes outside the account-keeping implementation, tests, and documentation. Keep only formatting changes required for files that implement or test [#28683].

Full details: Docstring Coverage

Explanation

Docstring coverage is 39.31% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 173 functions across 117 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 68216c22-a0d9-4a9f-878a-eb405d7c4e35
📥 Commits

Reviewing files that changed from the base of the PR and between c1597c3 and ac1a40c.

📒 Files selected for processing: 124
  • docs/site/content/docs/agents/claude-code.mdx
  • docs/site/content/docs/agents/codex-hot-swap.mdx
  • src/main/agent-accounts/agent-account-keep-wiring.ts
  • src/main/agent-accounts/agent-account-keep.test.ts
  • src/main/agent-accounts/agent-account-keep.ts
  • src/main/agent-accounts/agent-launch-account.test.ts
  • src/main/agent-accounts/agent-launch-account.ts
  • src/main/agent-accounts/agent-run-account-adoption.ts
  • src/main/agent-accounts/agent-run-account-compute.ts
  • src/main/agent-accounts/agent-run-accounts-installed.ts
  • src/main/agent-accounts/agent-run-accounts.test.ts
  • src/main/agent-accounts/agent-run-accounts.ts
  • src/main/agent-accounts/kept-account-refusals.ts
  • src/main/agent-accounts/kept-agent-conversations.test.ts
  • src/main/agent-accounts/kept-agent-conversations.ts
  • src/main/agent-accounts/kept-codex-home.ts
  • src/main/agent-hooks/server-claude-statusline.test.ts
  • src/main/agent-hooks/server-last-status-hydrate-validation.test.ts
  • src/main/agent-hooks/server-run-account.test.ts
  • src/main/agent-hooks/server.ts
  • src/main/agent-hooks/server/server-lifecycle.ts
  • src/main/agent-hooks/server/server-persistence-validation.ts
  • src/main/agent-hooks/server/server-row-ownership.ts
  • src/main/agent-hooks/server/server-run-account-carry.ts
  • src/main/agent-hooks/server/server-run-account.ts
  • src/main/agent-hooks/server/server-status-identity.ts
  • src/main/agent-hooks/server/server-status-update.ts
  • src/main/agent-hooks/server/server-types.ts
  • src/main/claude-accounts/claude-credential-persistence-ratchet.test.ts
  • src/main/claude-accounts/claude-profile-account-pointers.test.ts
  • src/main/claude-accounts/claude-profile-account-pointers.ts
  • src/main/claude-accounts/claude-profile-account-route.ts
  • src/main/claude-accounts/claude-profile-router.ts
  • src/main/claude-accounts/runtime-auth-service-kept.test.ts
  • src/main/claude-accounts/runtime-auth-service.ts
  • src/main/codex-accounts/runtime-home-kept-resume.test.ts
  • src/main/codex-accounts/runtime-home-service-home-routing.ts
  • src/main/codex-accounts/runtime-home-service-state.ts
  • src/main/codex/codex-ai-vault-session-resume-kept.test.ts
  • src/main/codex/codex-ai-vault-session-resume.ts
  • src/main/codex/codex-pane-account-registry-kept.test.ts
  • src/main/codex/codex-pane-account-registry-mutations.test.ts
  • src/main/codex/codex-pane-account-registry-mutations.ts
  • src/main/codex/codex-pane-account-registry-types.ts
  • src/main/codex/codex-pane-account-registry.ts
  • src/main/codex/codex-stale-pane-accounts.test.ts
  • src/main/codex/codex-stale-pane-accounts.ts
  • src/main/ipc/agent-account-keep.test.ts
  • src/main/ipc/agent-account-keep.ts
  • src/main/ipc/codex-accounts-keep-stale-panes.test.ts
  • src/main/ipc/codex-accounts.ts
  • src/main/ipc/pty-claude-kept-launch.test.ts
  • src/main/ipc/pty-codex-kept-resume.test.ts
  • src/main/ipc/pty-kept-account-spawn.test-fixture.ts
  • src/main/ipc/pty/host-env/agent-account-launch.ts
  • src/main/ipc/pty/host-env/codex-home.ts
  • src/main/ipc/pty/host-env/codex-resume-kept.test.ts
  • src/main/ipc/pty/host-env/codex-resume.ts
  • src/main/ipc/pty/host-env/types.ts
  • src/main/ipc/pty/ipc/spawn-commit-persist.ts
  • src/main/ipc/pty/ipc/spawn-commit.ts
  • src/main/ipc/pty/ipc/spawn-env-codex.ts
  • src/main/ipc/pty/ipc/spawn-preflight.ts
  • src/main/ipc/pty/ipc/spawn-state.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/register-core-handlers/register-core-handlers.test.ts
  • src/main/ipc/register-core-handlers/register-core-handlers.ts
  • src/main/persist-retry-backoff.ts
  • src/main/startup/codex-session-resume-kept.test.ts
  • src/main/startup/codex-session-resume-launch.ts
  • src/main/startup/main-process-account-services.ts
  • src/main/startup/main-process-agent-run-accounts.test.ts
  • src/main/startup/main-process-agent-run-accounts.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/main/startup/main-window-agent-status.ts
  • src/main/startup/main-window-core-services.ts
  • src/main/startup/main-window-structured-status-filter.test.ts
  • src/preload/api-types.ts
  • src/preload/api/agent-account-api.ts
  • src/preload/api/agent-account-keep-bridge.ts
  • src/preload/api/codex-accounts-bridge.ts
  • src/preload/index.ts
  • src/renderer/src/components/CodexRestartChip.tsx
  • src/renderer/src/components/codex-restart-chip.test.tsx
  • src/renderer/src/components/settings/KeptAgentsRemovalNote.tsx
  • src/renderer/src/components/settings/accounts-pane-removal-dialogs.tsx
  • src/renderer/src/components/settings/accounts-pane-removal-kept-agents.test.tsx
  • src/renderer/src/components/status-bar/AccountAgentCount.tsx
  • src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx
  • src/renderer/src/components/status-bar/CodexSwitcherMenu.tsx
  • src/renderer/src/components/status-bar/count-terminal-agents-by-account.test.tsx
  • src/renderer/src/components/status-bar/count-terminal-agents-by-account.ts
  • src/renderer/src/components/status-bar/switcher-agent-count.test.tsx
  • src/renderer/src/components/tab-bar/SortableTab.tsx
  • src/renderer/src/components/tab-bar/SortableTabContextMenu.tsx
  • src/renderer/src/components/tab-bar/TabAgentAccountLines.test.tsx
  • src/renderer/src/components/tab-bar/TabAgentAccountLines.tsx
  • src/renderer/src/components/tab-bar/TabAgentAccountMenuItems.test.tsx
  • src/renderer/src/components/tab-bar/TabAgentAccountMenuItems.tsx
  • src/renderer/src/components/tab-bar/TabHoverCard.tsx
  • src/renderer/src/components/tab-bar/tab-agent-accounts.test.ts
  • src/renderer/src/components/tab-bar/tab-agent-accounts.ts
  • src/renderer/src/components/tab-bar/use-tab-agent-accounts.ts
  • src/renderer/src/components/terminal-pane/TerminalPaneRuntimePortals.tsx
  • src/renderer/src/hooks/ipc-events/normalize-agent-status-event.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/slices/agent-status-contract.ts
  • src/renderer/src/store/slices/agent-status-live-entry-builder.ts
  • src/renderer/src/store/slices/agent-status-live-entry-run-account.ts
  • src/renderer/src/store/slices/agent-status-run-account.test.ts
  • src/renderer/src/web/preload-api/web-agent-accounts-api.ts
  • src/shared/agent-run-account.test.ts
  • src/shared/agent-run-account.ts
  • src/shared/agent-status-ipc-payload.ts
  • src/shared/agent-status-types.ts
  • src/shared/claude-profile-routing.ts
  • src/shared/claude-shell-function.test.ts
  • src/shared/claude-statusline-rate-limits.ts
  • tests/e2e/agent-account-keep-claude.spec.ts
  • tests/e2e/agent-account-keep-codex.spec.ts
  • tests/e2e/fixtures/claude-account-probe/claude
  • tests/e2e/helpers/agent-hook-endpoint.ts

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

Comment thread src/main/agent-accounts/agent-account-keep.ts
Comment thread src/main/agent-accounts/kept-agent-conversations.ts
@QichangZheng

Copy link
Copy Markdown
Author

Hi @brennanb2025, thanks for picking this up. Before you dig into the diff, here is the whole plan this PR belongs to, so you can tell me early whether the direction works for Orca.

Goal: agent-level account scheduling. Today Orca has one selected Claude account and one selected Codex account, and every agent follows it. The idea is to make the account a property of each agent, so different agents in one Orca can run on different accounts at the same time, and work can be spread across subscriptions instead of all agents moving together when one account runs low.

I'd like to get there in three PRs, each one mergeable on its own and each building on the previous one:

  1. Each agent keeps its own account (this PR, feat(accounts): keep a Claude or Codex agent on its account #28684). Every Claude/Codex agent tab shows which account it runs on, and a per-agent "Keep on <account>" keeps it there through account switches, restarts, sleep/wake, cold restore and resume. The switchers show how many agents each account carries. Agents nobody keeps behave exactly as today, and a kept start that can't be honoured refuses instead of running on another account.
  2. Place new agents where there is room (opt-in, off by default). When enabled, each agent start that isn't kept picks the account on its lane with the longest runway before a configurable line (default 90%), using only usage readings Orca can trust (attributed to the right account, fresh, read successfully; blocked or signed-out accounts are never targets). An account at its line takes no new agents until its window resets. When every account is at its line, Orca stays put and says when the first one frees up. This is the part [Feature]: Automatic account failover on rate limit for managed Claude/Codex subscription accounts #20512 asks for.
  3. Move idle agents off an account at its line (opt-in). Agents on that account that have finished their turn and sat idle (cache cold) get a one-click "Continue on <account>", which resumes the same conversation on another account; a separate setting does it automatically. An agent that is generating, waiting for input or permission, or kept is never moved.

Throughout: no login is ever copied or moved. Agents on one account keep sharing that account's folder/home as today, because refresh tokens rotate as one chain and copies would invalidate each other.

This PR is step 1, and it's deliberately the foundation the other two need: the per-agent account on the Agent Status row, the kept mode, one resolver both spawn paths call, the per-account Claude pointer, and per-account Codex home resolution for resume.

I've paused steps 2 and 3 until I hear from you. If the direction looks right, I'll continue; if you'd rather this land differently (for example split into a Codex PR and a Claude PR, or scoped down further), I'm happy to restructure.

…he next read

A keep or un-keep made while the kept-agents file could not be read waited for
the next retry. A launch in between started on the selected account, and once
the retries ran out nothing wrote the change down. The first read after the
file is readable again now applies and writes the queued change; a restart
before any such read still loses it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QichangZheng and others added 2 commits October 14, 2026 21:50
Un-keeping from the tab menu reported success and dropped the kept marker while
the newer Orca's list went on holding the keep. It now answers that this version
cannot change the list, as keeping already did, and leaves the list alone.

A Codex pane's own keep lives in its launch record, not in that list, and
"Keep old account" can still set it there. Un-keeping such a pane still clears
the record, so the tab menu can undo what the prompt kept.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unch registry

Un-keeping first asked the launch registry whether the pane had a record, and
an unreadable registry answered "no record". With the kept-agents list
writable, un-keeping then reported success and dropped the kept marker while
the launch record stayed kept, so the pane was kept again once the registry
was readable. With the list owned by a newer Orca it answered read-only.

It now goes by the release's own answer: an unreadable registry reports
unavailable so the user can retry, and only a pane with no record is treated
as having nothing in the registry to let go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adifsgaid

Copy link
Copy Markdown

@QichangZheng your step 3 is the thing I've been prototyping from the other side. #28044 switches the selected account before a limit, and the obvious gap (#19900) is that running agents don't follow. Rather than build a second version next to yours, I'd like to help with step 3 on top of step 1 once it lands, or leave it to you if you're already on it.

A few things that came up while working through the cases, in case they save you time:

  • Only done is safe to move. A pane on a permission prompt or an AskUserQuestion reports waiting, which is still mid-turn.
  • Claude's StopFailure hook carries an error field, so a turn that died on the limit can be told apart from a normal Stop. That's the one case where sending "continue" after the resume makes sense. After a normal stop it would start work nobody asked for.
  • A restart throws away anything typed but not sent, so a pane with unsent input should wait.
  • If the account switches back before a busy pane finishes, its move should just drop.
  • A pane with no session id can't be resumed and shouldn't be restarted fresh.

The cases are in one clickable HTML file if it helps (download it and open it, nothing to build): https://github.com/adifsgaid/orca/blob/prototype/resume-on-switch/src/renderer/src/lib/PROTOTYPE-resume-on-account-switch.html

On step 2 and #28044: they overlap. #28044 moves the selected account, your step 2 picks an account for each new agent. @brennanb2025 if you prefer one shape, I'm happy to fold the #28044 policy (claude-account-auto-switch-policy.ts: the 90% line, the margin and the cooldown) into step 2 instead of keeping both.

@adifsgaid

Copy link
Copy Markdown

@QichangZheng I opened #29802 for the switch-driven half of #19900: after you switch, it moves the running Claude sessions to the new account with claude --resume.

It doesn't overlap with this one. Today it reads a session's account from its transcript path, all in one function (claude-transcript-account.ts). Once step 1 here lands, that becomes a lookup of the per-agent account, and a kept pane gets skipped. Whichever of us merges second rebases. Step 3, moving a session because it hit its limit, stays with you.

QichangZheng and others added 5 commits October 17, 2026 08:42
Kept-account launches now ride main's shared spawn steps: both preflights resolve the
account launch from the startup target before Claude auth, the kept Codex account reaches
resume prep through resolvePtySpawnHostEnv (new optional keptAccount), and the kept-resume
rules moved into the resume-launch factory the desktop and orcad share. Facet-only
republishes still skip telemetry; titles and first-work renames now hang off the hook
subscription, which they never reach. The window lane notes a pane's launch before seeding
its resume identity, as the runtime lane already did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le from the tab menu

The restored-row sweep retires a dead terminal's row to an identity row and
clears the pane. The identity row keeps the account facet, but the window
dropped it from identity rows and the renderer drops every non-Pi identity
row, so the tab menu and hover card lost the agent. A refused kept resume
then told the person to un-keep it from a tab menu item that was gone.

- The hook server restates the facet after a retirement that keeps resume
  identity (after its pane clear), as a facet-only replay.
- The window forwards the facet on identity rows, including a withdrawal.
- The renderer mirrors identity-row facets in a small store; the tab menu,
  hover card and per-account counts read it. An identity row's facet wins
  until a newer status row takes the pane back, and a pane clear forgets it.
  Remote and stale identity rows never name an account.

Status rows, the sweep itself and rows without a facet behave as before.
The same path now shows a resumed pane's facet before its first turn.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keeps main's native-adoption resume preparation and passes the kept-home
resolver through it; drops the Claude switcher footer note as main does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Claude agent kept on the outgoing account was counted as still running there,
listed in the switch dialog and queued by Restart and Always restart, only to
resume on its kept account again. The switch now leaves kept agents out of the
count and the list, asks nothing when only kept agents remain, and a queued
restart of a pane that became kept is dropped instead of relaunching it.
Agents that follow the selection restart exactly as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t, and out of the switch restart

An idle Claude terminal that outlived an Orca restart reattaches without posting
anything, so the producer had no observation of it and withdrew the kept facet it
had just been hydrated with. The switch dialog then counted, listed and restarted
that kept agent again. A pane this runtime has not seen report now keeps the account
an earlier runtime recorded, reading only the keep afresh; a fresh spawn still
starts from nothing.

A conversation kept on one account but running on another (resumed by hand in a
selection-following terminal) showed as following, yet its restart resumes on the
kept account. The facet now says so, and the switch leaves such an agent out like a
kept one. Agents that follow the selection restart exactly as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@QichangZheng

Copy link
Copy Markdown
Author

@adifsgaid thanks, this is useful. Your cases match what I had in mind for step 3: only move panes that are done, use StopFailure's error to tell a limit stop from a normal stop, never restart a pane with unsent input, drop the move if the account switches back, and skip panes without a session id. I'll write step 3 against that list.

Now that #29898 is on main, I've updated this PR so its switch dialog leaves kept agents alone: a kept agent stays on its account, and every other session follows the switch the way #29898 does today. I've also merged current main to clear the new conflicts. I'll start step 2 once @brennanb2025 is happy with the overall direction.

@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: be31eff6-30c7-4256-961e-598318d8ae1a
📥 Commits

Reviewing files that changed from the base of the PR and between f42f415 and 91c602e.

📒 Files selected for processing: 20
  • docs/site/content/docs/agents/claude-code.mdx
  • src/main/agent-accounts/agent-run-account-compute.ts
  • src/main/agent-accounts/agent-run-accounts.test.ts
  • src/main/agent-accounts/agent-run-accounts.ts
  • src/main/claude-accounts/claude-profile-router.ts
  • src/main/codex/codex-ai-vault-session-resume.ts
  • src/main/startup/main-process-agent-run-accounts.test.ts
  • src/main/startup/main-process-runtime-service.ts
  • src/renderer/src/components/codex-restart-chip.test.tsx
  • src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx
  • src/renderer/src/components/status-bar/switcher-agent-count.test.tsx
  • src/renderer/src/components/terminal-pane/claude-detached-pane-restart.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/lib/agent-account-restart-startup.test.ts
  • src/renderer/src/lib/agent-account-restart-startup.ts
  • src/renderer/src/lib/claude-session-account-move.test.ts
  • src/renderer/src/lib/claude-session-account-move.ts
  • src/renderer/src/store/agent-identity-row-run-accounts.ts
  • src/shared/agent-run-account.test.ts
  • src/shared/agent-run-account.ts
🚧 Files skipped from review as they are similar to previous changes: 1
  • src/renderer/src/i18n/locales/en.json

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

Comment on lines +58 to +63
// Why: a kept conversation retargets to its own account, never to the selection.
const keptHome =
!args.fork && args.sessionId && args.executionHostId === LOCAL_EXECUTION_HOST_ID
? options.resolveKeptCodexHome?.(args.sessionId)
: undefined
const keptOnSystemDefault = keptHome !== undefined && sameHome(keptHome, getSystemCodexHomePath())

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
rg -nP -C8 '\bcodexAiVaultSessionResumePreparation\b' --type=ts
rg -nP -C5 '\bprepareAiVaultSessionResume\s*\(' --type=ts -g '!**/*.test.ts'

Repository: stablyai/orca

Length of output: 13328


Add a Codex agent check before the kept-home lookup.

The generic Session History resume path passes Claude and Codex requests to this preparer. Without the agent check, a non-Codex local session can call resolveKeptCodexHome with its session ID. This can apply a Codex keep entry to another agent or propagate keptAccountRefusals.indexUnavailable to that resume.

🐛 Suggested fix
   const keptHome =
-    !args.fork && args.sessionId && args.executionHostId === LOCAL_EXECUTION_HOST_ID
+    args.agent === 'codex' &&
+    !args.fork &&
+    args.sessionId &&
+    args.executionHostId === LOCAL_EXECUTION_HOST_ID
       ? options.resolveKeptCodexHome?.(args.sessionId)
       : undefined
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Why: a kept conversation retargets to its own account, never to the selection.
const keptHome =
!args.fork && args.sessionId && args.executionHostId === LOCAL_EXECUTION_HOST_ID
? options.resolveKeptCodexHome?.(args.sessionId)
: undefined
const keptOnSystemDefault = keptHome !== undefined && sameHome(keptHome, getSystemCodexHomePath())
// Why: a kept conversation retargets to its own account, never to the selection.
const keptHome =
args.agent === 'codex' &&
!args.fork &&
args.sessionId &&
args.executionHostId === LOCAL_EXECUTION_HOST_ID
? options.resolveKeptCodexHome?.(args.sessionId)
: undefined
const keptOnSystemDefault = keptHome !== undefined && sameHome(keptHome, getSystemCodexHomePath())

QichangZheng and others added 2 commits October 18, 2026 23:16
The tab context menu imports both the agent account items and the new
close-split item. SortableTab was at its 400-line limit on main, so the
terminal tab's hover card (with its account lines) moves to its own
TerminalTabHoverCard. Since stablyai#29884 a host spawn names its pane from its own
tab and leaf ids, so the kept-launch host test passes those instead of
ORCA_PANE_KEY in the caller's env.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…econds

A millisecond mtime set through utimes can read back as 691.999 instead of
692, so the "writes only when the route changed" check failed now and then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

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]: Keep a Claude or Codex agent on its account across switches and restarts

2 participants