Skip to content

feat: several Claude subscriptions, bound per session - #10

Open
pocharlies wants to merge 1 commit into
otto-assistant:mainfrom
pocharlies:feat/multi-account-core
Open

feat: several Claude subscriptions, bound per session#10
pocharlies wants to merge 1 commit into
otto-assistant:mainfrom
pocharlies:feat/multi-account-core

Conversation

@pocharlies

Copy link
Copy Markdown

One OpenCode server drives several Claude subscriptions at once, with each
session pinned to one — this chat on work, that one on personal.

Each account is a CLAUDE_CONFIG_DIR. That shape is forced by the rotation
constraint this codebase already documents in auth-login.ts: Anthropic rotates
the refresh token on every use, and a chain with two owners gets the whole grant
revoked for replay. Giving each account its own CLI home keeps exactly one owner
per chain — the CLI — so accounts cannot race each other's rotation. The plugin
reads those credentials and never rotates them.

Selection rides the model id. opus@personal, shown as Opus 5 · Personal,
flows through the existing chat.headersEFFORT_HEADER path, so it appears
in the host's picker with no UI work. The first turn binds the session; later
turns stay on that account even when the request carries none.

Everything previously global is now keyed by account:

  • rate-limit state and the 429 fast-fail gate — an exhausted subscription
    used to block every other account, which defeats having them. Existing
    single-account stores migrate on read.
  • session bindings, and resume lookups against the owning account's
    transcript dir. Moving accounts drops the stale resume target rather than
    continuing a foreign conversation in another home.
  • the credential probe and credential reads, which now refuse to fall back
    to the ambient ~/.claude for a scoped account — that would silently run the
    turn on the wrong subscription.

Declaring no accounts changes nothing: same model ids, same stores, same
behaviour. Configure with the accounts plugin option,
OPENCODE_CLAUDE_ACCOUNTS, or accounts.json.

One caveat worth stating: CLAUDE_CONFIG_DIR also relocates settings, skills
and the user-level CLAUDE.md, so shared files need symlinking into each home.
Documented in the README.

Also isolates XDG_DATA_HOME for the test run — the suite was reading the
operator's real accounts.json (so "nothing configured" stopped being true once
they had accounts), writing fixture sessions into their store, and unlinking
their debug.log.

bun test passes and tsc is clean. Independent of #9.

One OpenCode server can drive several Claude subscriptions at once, with each
session pinned to one: this chat on `work`, that one on `personal`.

Each account is a CLAUDE_CONFIG_DIR — a self-contained Claude CLI home with its
own credentials, transcripts and settings. That shape is forced by the rotation
constraint this codebase already documents in auth-login.ts: Anthropic rotates
the refresh token on every use, and a chain with two owners gets the whole grant
revoked for replay. Giving each account its own CLI home keeps exactly one owner
per chain — the CLI — so accounts cannot race each other's rotation. The plugin
reads those credentials and never rotates them.

Selection rides the model id (`opus@personal`, named `Opus 5 · Personal`) so it
flows through the existing chat.headers → EFFORT_HEADER path and appears in the
host's model picker with no UI work. The first turn binds the session; later
turns stay on that account even when the request carries none.

Everything previously global is now keyed by account:

- rate-limit state and its 429 fast-fail gate — an exhausted subscription used
  to block every other account, which defeats having them. Existing
  single-account stores migrate on read.
- session bindings, and resume lookups against the owning account's transcript
  dir; moving accounts drops the stale resume target rather than continuing a
  foreign conversation.
- the pre-flight credential probe and credential reads, which now refuse to
  fall back to the ambient ~/.claude for a scoped account — that would silently
  run the turn on the wrong subscription.

Declaring no accounts leaves behaviour unchanged: same model ids, same stores,
same everything. Configure via the `accounts` plugin option,
OPENCODE_CLAUDE_ACCOUNTS, or accounts.json.

Also isolates XDG_DATA_HOME for the test run. The suite was reading the
operator's real accounts.json — so "nothing configured" stopped being true once
they had accounts — writing fixture sessions into their store, and unlinking
their debug.log.
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.

1 participant