Skip to content

Derive boot sidebar active account from routing mode and preserve live session state - #128

Merged
ualtinok merged 1 commit into
cortexkit:mainfrom
iceteaSA:fix/sidebar-active-boot
Jul 19, 2026
Merged

Derive boot sidebar active account from routing mode and preserve live session state#128
ualtinok merged 1 commit into
cortexkit:mainfrom
iceteaSA:fix/sidebar-active-boot

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

On boot the sidebar always showed main as the active account, even with routing.mode: fallback-first — and a freshly launched session clobbered the shared sidebar-state file's activeId that another live session's routing had legitimately written. The display only self-corrected after the new session's first real request.

Three contributing sites in packages/opencode/src/index.ts:

  • the auth-loader boot seed hardcoded activeId: 'main', route: 'main'
  • the in-process lastSidebarRouting default also started at 'main', so background quota refreshes kept re-writing it
  • one command path hardcoded the same values, so running a /claude-* command reset the displayed active account

Fix

New resolveInitialSidebarRouting(storage) used to seed both the boot write and lastSidebarRouting:

  1. If the existing sidebar file has a fresh activeId (recent lastUpdated, id still present among enabled accounts), preserve it — that's another live session's routing decision.
  2. Otherwise derive from the routing mode: fallback-first with at least one enabled OAuth fallback → that fallback, else main.

The command path now reuses lastSidebarRouting (the pattern the mutation-refresh path already used). Real routing decisions keep overwriting lastSidebarRouting exactly as before.

Verification

Six red-first tests: fallback-first boot derives the fallback; a fresh cross-session activeId is preserved; stale or invalid file state falls back to derivation; /claude-quota no longer resets the active account; main-first boot unchanged. Full suite: core 37 / opencode 812 / pi 48, typecheck + biome clean, repo-root bun run test green.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Derives the sidebar’s active account on boot from routing mode and preserves a live session’s selection across boots and display-only refreshes. Cross-process writes are serialized and atomic to prevent clobbering.

  • Bug Fixes
    • Boot resolves routing after loading accounts, then reads the sidebar: preserve a fresh (≤10m) valid activeId; otherwise derive from routing (fallback-first → first enabled OAuth, else main). Ignores stale/unknown ids and keeps existing routing if storage reload fails.
    • Display-only writes (boot, quota refresh, notice, command) are non‑authoritative: re-read the file and fresh accounts, keep newer routing, carry forward latest quota/accounts, and update in‑process lastSidebarRouting when preserved. Real routing decisions stay authoritative; commands now reuse lastSidebarRouting.
    • Cross-process writes use a per-file directory lock with owner tokens and jittered retries; stale locks are evicted via atomic rename. Writes use temp files and atomic rename with ownership checks before/after; on post-rename loss, one bounded repair republishes routing/UI fields into the successor’s fresh state. Skips when lock budget is exhausted; never writes unlocked.

Written for commit d6d3de6. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR keeps sidebar routing aligned with the current routing mode and live session state. The main changes are:

  • Derive boot routing from fresh account storage.
  • Preserve fresh, valid routing from another session.
  • Reconcile display-only writes under a cross-process lock.
  • Publish sidebar state atomically and handle lock loss.
  • Add tests for boot, refresh, command, and concurrency paths.

Confidence Score: 5/5

This looks safe to merge.

  • The updated boot and display-only paths preserve current shared routing.
  • Lock ownership and publication races have focused tests.
  • No blocking issues were found in the changed code.

Important Files Changed

Filename Overview
packages/opencode/src/index.ts Derives and validates sidebar routing, preserves routing on reload failure, and marks display-only writes as non-authoritative.
packages/opencode/src/sidebar-state.ts Adds cross-process locking, atomic state publication, ownership checks, and bounded lock-loss repair.
packages/opencode/src/tests/index.test.ts Covers boot derivation, cross-session preservation, invalid state, reload failures, commands, and authoritative routing.
packages/opencode/src/tests/sidebar-state.test.ts Covers lock contention, stale eviction, ownership handoff, skipped writes, atomic publication, and lock-loss races.

Reviews (17): Last reviewed commit: "fix(opencode): derive boot sidebar activ..." | Re-trigger Greptile

Context used:

  • Context used - captures/AGENTS.md (source)
  • Context used - AGENTS.md (source)

Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/index.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/sidebar-state.ts Outdated
Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/sidebar-state.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/sidebar-state.ts Outdated
Comment thread packages/opencode/src/tests/index.test.ts Outdated
Comment thread packages/opencode/src/tests/index.test.ts
Comment thread packages/opencode/src/sidebar-state.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/sidebar-state.ts
Comment thread packages/opencode/src/sidebar-state.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/sidebar-state.ts Outdated
Comment thread packages/opencode/src/tests/sidebar-state.test.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/tests/index.test.ts
@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from 77481fe to 8563459 Compare July 17, 2026 21:56
Comment thread packages/opencode/src/sidebar-state.ts
@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from 8563459 to 3db8746 Compare July 17, 2026 22:22
Comment thread packages/opencode/src/sidebar-state.ts Outdated
@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from 3db8746 to 934a7ff Compare July 17, 2026 22:43
Comment thread packages/opencode/src/sidebar-state.ts
@ualtinok

Copy link
Copy Markdown
Contributor

The latest version typechecks and its focused tests pass, but one cross-process race remains in the core mechanism.

resolveInitialSidebarRouting() reads the existing sidebar snapshot first, then resolveFreshSidebarRouting() awaits loadFreshStorage(), and afterward may restore the previously captured route if it is under ten minutes old. Another process can publish a newer authoritative route during that await; this process will then overwrite it with the stale pre-load snapshot. The freshness window does not detect that interleaving.

Please make the final routing decision against a sidebar snapshot read after the asynchronous storage load, or use a version/CAS/lock scheme that cannot overwrite a newer writer. Add an interleaving regression where process B writes a new route while process A's storage load is paused, then assert A preserves B's route.

The same-process preservation behavior is useful; this needs the cross-process write ordering fixed before merge.

@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from 934a7ff to 882becf Compare July 18, 2026 19:35
@iceteaSA

Copy link
Copy Markdown
Contributor Author

Fixed in 882becf. Boot now completes the asynchronous account-storage load first and only then reads the sidebar snapshot for the preserved-route decision, so a peer's authoritative publish during the load can no longer be overwritten by a pre-load capture. The 10-minute freshness window and active-account validation are unchanged and still evaluate against fresh-loaded storage; the subsequent write remains non-authoritative and re-reads under the existing lock.

Added your exact interleaving as a regression: process A's storage load is paused via a test hook, process B publishes a newer authoritative route mid-pause, A resumes — B's route survives in the file. Red on the pre-fix ordering (sidebar captured at sequence 1, storage completed at sequence 2), green after.

@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch 2 times, most recently from d479727 to 9554b68 Compare July 19, 2026 08:46
@ualtinok

Copy link
Copy Markdown
Contributor

The original ordering issue is fixed: the new interleaving regression confirms that storage loading completes before the preserved sidebar snapshot is read, so a peer's route written during the load survives.

One current-main integration failure remains. On a merge with v1.16.0:

964 pass
1 fail

The failing test is auth.loader > stale writer does not resurrect a deleted active account: expected work-current, received work-deleted.

The fixture uses ordinary saveAccounts() to simulate deleting work-deleted, but v1.16.0 intentionally preserves unmatched accounts during ordinary concurrent saves. Please rebase onto current main and change the fixture to use the explicit persistent removal path (removeAccountPersistent, or the corresponding save option with removedAccountIds). Then rerun the complete unit and E2E gates. I did not find another implementation blocker after the ordering fix.

@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from 9554b68 to de51f1a Compare July 19, 2026 17:32
@iceteaSA

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (v1.16.0) — de51f1a. The failing fixture was exactly what you diagnosed: the test simulated deletion with an ordinary saveAccounts(), which under v1.16.0's union-merge no longer removes the account. It now declares the deletion explicitly via saveAccounts(..., { removedAccountIds: ['work-deleted'] }), matching removeAccountPersistent's behavior.

Full gates on the rebase: opencode 866, e2e 21, typecheck/lint clean.

Comment thread packages/opencode/src/index.ts
…nd preserve live session state

Boot seeding, quota-only refreshes, and command paths no longer clobber
the sidebar activeId written by a live routing session. Routing-authoritative
writes are distinguished from display-only writes; the serialized write chain
re-reads the file before merging, and cross-process writes are guarded by an
mkdir directory lock (atomic rename-claim eviction, jittered retry, file-based
ownerId release). On lock-budget exhaustion against an active contender the
write is skipped — never performed unlocked. The commit path is fenced:
ownership is re-verified adjacent to the rename and again after it; on
detected post-rename loss, one bounded locked repair republishes only the
frame's routing-authoritative fields into the successor's fresh state. Boot
resolves preserved routing only after the asynchronous account-storage load
completes, so a peer's authoritative publish during the load cannot be
overwritten by a pre-load snapshot. The residual window is a process freeze
exactly between the final ownership stat and the rename — accepted for a
display-only file whose frames self-heal.
@iceteaSA
iceteaSA force-pushed the fix/sidebar-active-boot branch from de51f1a to d6d3de6 Compare July 19, 2026 17:52
@ualtinok
ualtinok merged commit 23e4e7b into cortexkit:main Jul 19, 2026
5 checks passed
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.

2 participants