Skip to content

Add opt-in read-only claude-swap adapter for Claude multi-account usage#1909

Merged
steipete merged 7 commits into
steipete:mainfrom
optimiz-r:feat/claude-swap-multi-account
Jul 5, 2026
Merged

Add opt-in read-only claude-swap adapter for Claude multi-account usage#1909
steipete merged 7 commits into
steipete:mainfrom
optimiz-r:feat/claude-swap-multi-account

Conversation

@optimiz-r

@optimiz-r optimiz-r commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Phase 1 of the accepted Claude multi-account design: an opt-in, read-only claude-swap adapter that shows every account's session and weekly usage as stacked Claude cards.

Addresses the Claude-subscription portion of #1756 and #1268. Account switching and per-account status items remain out of scope.

User experience

  • Adds Providers → Claude → Read accounts from claude-swap, off by default.
  • Runs the explicitly configured executable with fixed argv: cswap --list --json.
  • Shows active account first; sentinel states such as expired/no credentials become account-specific notes.
  • Preserves the last successful account snapshot as visibly stale when a later adapter refresh fails.
  • Redacts account emails through the existing Hide Personal Info path.
  • Leaves the ambient Claude card and refresh path unchanged when disabled, missing, slow, or failing.

Maintainer hardening

  • Runs the optional adapter independently from the required Claude refresh; a 30-second adapter timeout cannot delay ambient usage.
  • Cancels and clears adapter work immediately when Claude, the adapter, or its executable path changes.
  • Rejects late results from an old executable/configuration and checks cancellation before every subprocess launch.
  • Rebuilds an open menu when background account results arrive.
  • Prevents account cards from inheriting ambient Claude costs, source labels, identity, or provider-level errors.
  • Requires a strict schema-v1 envelope, positive unique account slots, and one consistent active-account declaration.
  • Bounds subprocess runtime and output; never uses a shell or config-defined arguments.

Security boundary

  • CodexBar does not read claude-swap storage, Claude Code storage, or Keychain entries for this feature.
  • The external process owns any credential access. Raw stdout is neither logged nor persisted.
  • Only allow-listed display/usage fields enter in-memory provider-neutral snapshots; stable identity is claude-swap:<slot>, never an email or credential value.
  • Non-zero exits are accepted only so the schema-v1 error envelope on stdout can be parsed and surfaced.

Validation

Fake executables and model/state seams only; no real credentials or Keychain access:

  • swift test --filter 'ClaudeSwap|ClaudeProviderRuntimeTests|StatusMenuHostedSubmenuRefreshTests|MenuCardOverrideIsolationTests'
  • make check — SwiftFormat clean; SwiftLint 0 violations; docs/locales/repository checks pass.
  • make test — full 47-shard suite.
  • Maintainer autoreview: no accepted/actionable findings after fixes.

Focused coverage includes strict parsing, fixed argv, error envelopes, timeout/output bounds, cancellation-before-launch, stale/config result rejection, disabled-provider lifecycle, projection ordering, privacy redaction, open-menu readiness, and ambient account-data isolation.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed July 5, 2026, 10:11 AM ET / 14:11 UTC.

Summary
Adds an opt-in Claude setting and read-only cswap --list --json adapter that parses claude-swap account usage into provider-neutral snapshots and renders stacked Claude account cards, with tests and docs.

Reproducibility: yes. for the review finding. Source inspection shows that with more than one Claude token account, stacked layout, and more than one claude-swap snapshot, the token-account branch returns before claude-swap cards can render.

Review metrics: 2 noteworthy metrics.

  • Changed surface: 24 files, +1,539/-4. The PR spans provider settings/runtime, menu rendering, core parsing/process code, tests, and docs.
  • External process surface: 1 opt-in cswap --list --json adapter. Maintainers should review the process boundary and proof because this is a new provider integration path.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix the stacked Claude token-account plus claude-swap rendering conflict and add focused regression coverage.
  • [P1] Add redacted real behavior proof showing multiple claude-swap Claude account cards and ambient Claude remaining unaffected.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body reports fake-executable/model tests only; it still needs redacted real behavior proof such as a screenshot, recording, terminal output, live output, linked artifact, or logs before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A desktop/menu proof would materially help verify the user-visible stacked Claude account cards and stale/error behavior. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify CodexBar shows multiple claude-swap Claude account cards from a configured fake cswap and keeps ambient Claude usable when cswap fails.

Risk before merge

  • [P1] Users with multiple Claude token accounts and stacked account layout can enable claude-swap, fetch multiple snapshots, and still see only the older token-account cards.
  • [P1] The PR has strong synthetic coverage but no inspectable real setup proof showing multiple claude-swap cards and ambient Claude isolation after the change.

Maintainer options:

  1. Decide the mitigation before merge
    Land the accepted read-only adapter after resolving stacked-source rendering and adding redacted real behavior proof; keep account switching and per-account status items in later phases.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Contributor action is needed for the display blocker and real behavior proof; ClawSweeper should not queue a repair while the proof gate is mock-only.

Security
Cleared: No concrete security or supply-chain blocker found; the new subprocess path is opt-in, fixed-argv, no-shell, bounded, and strictly parsed.

Review findings

  • [P2] Render claude-swap before stacked token accounts — Sources/CodexBar/StatusItemController+Menu.swift:636
Review details

Best possible solution:

Land the accepted read-only adapter after resolving stacked-source rendering and adding redacted real behavior proof; keep account switching and per-account status items in later phases.

Do we have a high-confidence way to reproduce the issue?

Yes for the review finding. Source inspection shows that with more than one Claude token account, stacked layout, and more than one claude-swap snapshot, the token-account branch returns before claude-swap cards can render.

Is this the best way to solve the issue?

No for the current patch. The accepted adapter direction is sound, but the implementation should resolve the stacked-source conflict and include redacted real behavior proof before merge.

Full review comments:

  • [P2] Render claude-swap before stacked token accounts — Sources/CodexBar/StatusItemController+Menu.swift:636
    This prior finding is still present: the new claude-swap branch is placed after the existing stacked token-account branch, but that earlier branch returns immediately. In a supported setup with multiple Claude token accounts and multiAccountMenuLayout == .stacked, fetched claude-swap snapshots never reach this branch, so the opt-in adapter appears to do nothing.
    Confidence: 0.92

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against cda65e7000d1.

Label changes

Label justifications:

  • P2: This is a normal-priority feature PR with a concrete display blocker in a limited Claude multi-account configuration.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body reports fake-executable/model tests only; it still needs redacted real behavior proof such as a screenshot, recording, terminal output, live output, linked artifact, or logs before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

What I checked:

  • Repository policy read: Read the full AGENTS.md; its provider-data siloing, no-live-Keychain-probe, focused-test, and PR proof guidance apply to this Claude integration review. (AGENTS.md:1, cda65e7000d1)
  • Accepted Phase 1 boundary: The current design document accepts a disabled-by-default, exact-argv, read-only claude-swap adapter and says implementation should not merge until independent adapter/model proof is complete. (docs/claude-multi-account-and-status-items.md:11, cda65e7000d1)
  • Stacked token-account branch still preempts claude-swap: At PR head, addMenuCards returns from the existing token-account showAll branch before reaching the new claude-swap branch, so stacked Claude token accounts can hide fetched claude-swap cards. (Sources/CodexBar/StatusItemController+Menu.swift:610, 139a752702c4)
  • Existing stacked condition: tokenAccountMenuDisplay sets showAll when there is more than one token account and the multi-account menu layout is stacked, which is the condition that makes the earlier return fire. (Sources/CodexBar/StatusItemController+AccountMenuDisplay.swift:29, 139a752702c4)
  • Proof is mock-only: The PR body lists fake executables, model/state seams, focused tests, make check, and make test, but no inspectable real UI, terminal, log, screenshot, recording, or linked artifact proof. (139a752702c4)
  • Related product context: The owner comment on the Claude multi-account issue points to the accepted read-only claude-swap architecture and keeps broader switching/status-item decisions tracked separately.

Likely related people:

  • steipete: Git blame and PR/issue history tie this handle to the accepted Claude multi-account decision, the current stacked menu/token-account paths, and multiple hardening commits on this PR branch. (role: owner product-direction reviewer and recent area contributor; confidence: high; commits: 58968bd8b52e, e437044c32ba, d5253e2de345; files: docs/claude-multi-account-and-status-items.md, Sources/CodexBar/StatusItemController+Menu.swift, Sources/CodexBar/StatusItemController+AccountMenuDisplay.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (2 earlier review cycles)
  • reviewed 2026-07-05T11:07:04.026Z sha 52d7b54 :: needs real behavior proof before merge. :: [P2] Keep claude-swap from blocking the normal Claude refresh | [P3] Remove the release-owned changelog entry
  • reviewed 2026-07-05T13:51:02.064Z sha 139a752 :: needs real behavior proof before merge. :: [P2] Render claude-swap cards when token accounts are stacked

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52d7b54bf0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

if provider == .claude, self.shouldFetchClaudeSwapAccounts() {
await self.refreshClaudeSwapAccounts(generation: generation)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run claude-swap without blocking Claude refresh

When the opt-in adapter is enabled and the configured cswap executable is slow, locked on Keychain, or hangs until its timeout, this await delays the regular Claude provider fetch from even starting. That makes the optional adapter affect the ambient Claude card’s freshness/loading state despite the feature contract saying adapter failures should not affect ambient Claude usage; run it after or independently from the required Claude refresh path so a bad external executable cannot stall normal Claude updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed: the adapter now runs as a detached task that is never awaited by the ambient Claude refresh (scheduleClaudeSwapAccountRefresh in d5253e2, with state isolation and cancellation handling in 6430cd4/b8469c1c). As a follow-up, 139a752 also stops override account cards from inheriting the provider-level ambient error, so a broken ambient Claude credential can no longer render inside per-account claude-swap cards (covered in MenuCardOverrideIsolationTests).

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 5, 2026
optimiz-r and others added 5 commits July 5, 2026 05:58
…hots

Phase 1 of docs/claude-multi-account-and-status-items.md: execute exactly
'cswap --list --json' (no shell, bounded runtime and output), strictly parse
schema v1 with its error envelope and sentinel statuses, and project rows into
ProviderAccountUsageSnapshot keyed by claude-swap:<slot>. CodexBar reads no
claude-swap or Claude Code credential storage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opt-in via Preferences > Providers > Claude: a claude-swap toggle plus
executable path field. When enabled, each Claude refresh also runs the
adapter and renders one card per account (active first) using the Kilo
multi-scope pattern; adapter failures keep last-good rows as stale data and
surface in settings without touching the ambient Claude card. Hide Personal
Info redacts account emails as usual.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@steipete steipete force-pushed the feat/claude-swap-multi-account branch from 52d7b54 to b8469c1 Compare July 5, 2026 13:28
@clawsweeper clawsweeper Bot removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 5, 2026
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Owner

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Extend the override-card isolation from 6430cd4 to the lastError fallback:
the ambient Claude error (e.g. OAuth credentials unavailable) was rendering
inside every claude-swap account card. Apply the same surface scoping as the
snapshot, token-cost, and source-label fallbacks, with coverage in
MenuCardOverrideIsolationTests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00e1a83be0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return false
}

if context.currentProvider == .claude, self.store.claudeSwapAccountSnapshots.count > 1 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Render claude-swap cards even with stacked token accounts

When Claude already has more than one configured token account and the global multi-account layout is stacked, the earlier tokenAccountDisplay.showAll branch returns before this claude-swap branch can run. In that configuration, enabling claude-swap and successfully fetching multiple snapshots still silently shows only the token-account cards, so the new opt-in adapter does not render the accounts it just fetched; either combine the sources or make claude-swap precedence explicit for this case.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 9738b10: active claude-swap accounts now take explicit precedence over Claude token-account presentation — the claude-swap branch renders first in addMenuCards, and tokenAccountMenuDisplay(for:) returns nil for Claude while adapter rows are active, so the segmented switcher can't mix with adapter cards either. Covered in ClaudeSwapMenuPrecedenceTests (suppression for Claude, no effect on other providers) and documented in docs/claude.md.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction: 9738b10e missed the squash cut-off, so this is still open on main. The fix (plus ClaudeSwapMenuPrecedenceTests and the packaged synthetic proof) is now #1913.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 5, 2026
@steipete steipete merged commit 88e8a53 into steipete:main Jul 5, 2026
10 checks passed
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Landed after maintainer hardening and exact-head proof. Thanks @optimiz-r!

  • Exact contributor head: 139a752702c4ab314b46c26fb03bb86cb8aa6932
  • CI: all 10 checks green, including four macOS shards, Linux arm64/x64, lint, aggregate gate, and GitGuardian
  • Focused: swift test --filter 'ClaudeSwap|ClaudeProviderRuntimeTests|StatusMenuHostedSubmenuRefreshTests|MenuCardOverrideIsolationTests'
  • Full: make test (47 shards)
  • Checks: make check
  • Maintainer autoreview: no accepted/actionable findings
  • Squash merge: 88e8a537f2666528ae25da6fa3c845698f8931f6
  • Changelog: 9ad35deb

The final adapter is opt-in and read-only, cannot delay ambient Claude refreshes, rejects stale/config-mismatched results, preserves cancellation, strictly validates schema/identity invariants, and keeps account cards isolated from ambient costs, identity, source labels, and provider errors.

@optimiz-r

Copy link
Copy Markdown
Contributor Author

Packaged synthetic proof added in 9738b10 (fake cswap executable emitting schema-v1 fixtures — no real accounts, credentials, or provider calls): the Claude menu renders one stacked card per claude-swap account, active account first, with the token_expired sentinel as a per-account note. Also in docs/claude.md.

Stacked claude-swap account cards

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown

🦞🧹
ClawSweeper could not start a re-review for this item.

Reason: re-review requires an open issue or PR.

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

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants