Conversation
In remote-only mode the menu-bar icon serves from remoteCodexBarPrimarySnapshots, but iconObservationToken only observed local snapshot state. The status item therefore never re-rendered when served snapshots arrived, while the menu (menuObservationToken) did and rendered filled usage bars. Track remoteCodexBarSnapshots and remoteCodexBarPrimarySnapshots in iconObservationToken so icon updates fire on remote snapshot changes.
Ad-hoc replacement builds can lose access to a prior build's Keychain ACL. Previously Connect failed outright even though keeping the bearer token in process memory is safe for the current session. Allow an initial connection to proceed session-only when the secure write fails, while preserving the existing authority pair if a token replacement cannot be persisted. Explain that the token will be forgotten on quit and cover the fallback with a regression test.
The remote snapshot emitted one account per provider: the dashboard path collected only the selected account, and accounts[] was claude-swap only. Collect every token/Codex account, fold them into one provider row whose accounts[] carries them all, mark the serving Mac's selection active, and render remote accounts through the shared multi-account menu layout.
Add remote-only served provider mode shifted PreferencesProvidersPane.swift by one line, so the three allowlisted Codex account-state guards no longer matched their recorded anchors and the gatekeeper reported them as unjustified provider-specific constructs. Point the entries at lines 275/291/304.
The dashboard credential is a Keychain generic password whose access-control list names the binary that created it. A signature change invalidates that entry, so the preflight returns .interactionRequired, which loadCredential() collapsed into .temporarilyUnavailable. Every query is no-UI, so the user never saw a prompt: the token and the plain-HTTP consent silently went blank and had to be retyped after each update. Keep .interactionRequired distinct, and add loadCredentialAllowingInteraction(), which reads with Keychain UI allowed and then deletes and re-adds the item so the current binary owns the new record and later launches stay silent. Saving takes the same recovery path instead of failing, and the plain-HTTP consent now mirrors to UserDefaults alongside the server URL so it survives a failed read. SettingsStore attempts recovery once per launch and exposes an "Unlock Saved Token" control in Preferences for a declined prompt.
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs real behavior proof before merge. Reviewed September 20, 2026, 5:43 PM ET / 21:43 UTC (Revision 2). ClawSweeper reviewWhat this changesAdds a remote usage client in Sync settings, multi-account dashboard responses, and persistent endpoint-bound credentials with Keychain recovery. Merge readiness⛔ Blocked before merge - 17 items remain Keep open: this is meaningful work absent from current main, but all six prior findings remain on the unchanged head. The supplied successful-path evidence does not resolve the credential revocation and privacy blockers. Priority: P2 Review scores
Verification
How this fits togetherCodexBar collects provider usage locally and exposes dashboard snapshots through its CLI server. This change lets another Mac fetch those snapshots with a saved bearer token and display remote accounts in its menus and widgets. flowchart LR
A[Provider accounts] --> B[Dashboard server]
C[Sync settings] --> D[Endpoint and token in Keychain]
D --> E[Authenticated snapshot client]
B --> E
E --> F[Remote account projection]
F --> G[Menus and widgets]
C --> H[Local or remote-only mode]
H --> G
Decision needed
Why: The patch expands the data visible to existing dashboard clients; implementation intent alone does not establish acceptance of that upgrade contract. Before merge
Findings
Agent review detailsSecurityNeeds attention: The security pass found credential revocation and dashboard privacy defects; no unrelated dependency or supply-chain execution changes were introduced. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Provide durable, revocation-safe remote sync with a visible recovery menu, opaque exported account IDs, and an explicit account-export policy that preserves existing deployments unless maintainers approve expansion. Do we have a high-confidence way to reproduce the issue? Yes for the PR defects: source establishes the tombstone migration, raw account-ID export, destructive replacement, empty-menu, remote-only fallback, and anchor-mismatch paths. These are source findings, not executed reproductions or current-main failures. Is this the best way to solve the issue? Partly: reusing the dashboard contract and shared account layout is appropriate, but the credential state machine and export privacy need repair, and the broader default account scope needs an explicit decision. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against f3e718c897d5. LabelsLabel justifications:
EvidenceSecurity concerns:
What I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (1 earlier review cycle)
|
|
Thanks @VACInc. This adds the client side of |
Summary
High Level TLDR
CodexBar can already act as a server:
codexbar servepublishes a token-protected dashboard snapshot. Nothing could consume it. This adds the client half: a Sync settings pane that points CodexBar at a remotecodexbar serveendpoint and renders that machine's providers and accounts in the normal menus, plus an optional remote-only mode that skips local probes entirely.It also fixes two things the client half exposed: the serve payload only ever described a provider's selected account, and the saved bearer token was unreadable after any change to the app's code signature.
What this changes
Remote client
RemoteCodexBarSnapshotfetches and decodesGET /dashboard/v1/snapshot, with schema-version gating, a streaming response size limit, and last-good-snapshot retention for transient failures.Multi-account serve payload
serveand the one-shotdashboardcommand previously collected only the selected Codex/token account, so a provider row could never describe more than one account. They now enumerate every account and fold them into one provider entry carryingaccounts[], with the serving machine's active account marked and ordered first.AccountMenuLayoutPlannerpath as local ones, so stacked vs compact layout follows the existing user setting. Remote accounts are non-activatable from the client, so segmented layout goes compact from two accounts up.Keychain credential durability
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyand a non-secret tombstone instead of a delete, so a surviving legacy cache item is never silently re-migrated.Root Cause
Two defects, both found by running the client against a real
serveinstance.1. Single-account serve payload.
DashboardSnapshotBuilderwas invoked with the selected account only. The snapshot schema had no place to put siblings, so a machine with two signed-in Codex accounts served one and the client faithfully displayed one. Reproduced by diffing the one-shotdashboardJSON from a release build against this branch on the same machine with the same two accounts:2. Credential lost on signature change. The Keychain item's access-control list is bound to the creating binary's designated requirement. When the running binary is not on that list,
SecItemCopyMatchingreportserrSecInteractionNotAllowedrather than a missing item.KeychainRemoteCodexBarTokenStore.loadCredential()collapsed that state into.temporarilyUnavailable, and every query appliesKeychainNoUIQuery, so no authorization prompt could ever be presented. The token silently read back empty and Sync showed itself as unconfigured.The same stale record also blocked recovery by hand:
SecItemUpdateagainst an item the process cannot decrypt fails, so re-entering the token surfaced "could not be saved securely" and the user was wedged with no in-app path out.Fix: keep
.interactionRequireddistinct, and addloadCredentialAllowingInteraction(), which reads with Keychain UI allowed and then deletes and re-adds the record so the current binary owns the new ACL and subsequent launches are silent again. Deletion does not decrypt the payload, so it succeeds without the access the process is missing. Saving takes the same re-own path instead of failing.SettingsStoreattempts recovery at most once per launch and offers an explicit "Unlock Saved Token" control for a declined prompt. The plain-HTTP consent now mirrors toUserDefaultsnext to the server URL, so it survives a failed read the same way the endpoint already did.Real behavior proof
Two machines on a private network, one running
codexbar servebound off-loopback with a bearer token, one running the client.Multi-account payload, same machine and same two signed-in Codex accounts, release build vs this branch:
The client then rendered both Codex accounts in the menu through the existing multi-account layout.
Credential recovery, exercised against a real Keychain item created by a different code signature: the pre-fix binary read back an empty token with no prompt and rejected a re-entered token with "could not be saved securely"; the post-fix binary presented one authorization prompt, recovered the stored token and plain-HTTP consent, and read silently on every later launch of that binary.
What was not tested
Verification
make test— green, using the repository's suite-splitting harness.make format— clean../Scripts/package_app.sh release— succeeds, including the packaged-app launch smoke check.Pre-existing on this branch and not introduced here:
ProviderArchitectureGatekeeperTestsreports stale suppressed-reference anchors in files this PR does not touch, and SwiftLint reports three violations in pre-existing lines (UsageStore.swiftfile length, one long line, one trailing-closure style). Happy to fold the anchor cleanup in if you would rather have it here than as a follow-up.