feat(quota): background poll keeps idle account quota fresh - #56
Conversation
1399ceb to
e67f58c
Compare
There was a problem hiding this comment.
All reported issues were addressed
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
e67f58c to
823e46a
Compare
|
Thanks. Background quota refresh is useful, but this needs revision before merge:
Please rerun the complete gate after these changes. |
823e46a to
3751617
Compare
|
Thanks for the revision. Rebase and main-refresh lease use are now correct. Remaining blockers:
Please rerun the full gate after these changes. |
3751617 to
578efd0
Compare
|
Thanks for the revision. The per-loader lifecycle and in-flight stop work are now covered. Three blockers remain:
Please rerun the full gate after these fixes. |
885fd94 to
09f4d2f
Compare
|
Fixed in
Full gate green (748/0). The fallback-identity mechanism here is byte-identical to #57's ( |
|
Thanks, the live-identity binding and renewable background claim are now addressed. One blocker remains in the per-window merge: Propagate the entry timestamp onto each present window when serializing the sidebar state, or make |
09f4d2f to
4d443f0
Compare
|
Fixed in
Regression added through the normal writer path as requested: two identity-matched writes with crossed timestamps (A: both windows, snapshot T2, no window stamps — mirroring old files; B: stamped windows at T1 < T2). RED on the pre-fix code — B clobbered A's newer windows ( Retirement (absent slot in a fresher snapshot never resurrects) and identity-mismatch whole-pick behavior verified unchanged. Full gate: build + typecheck + 749/0 tests + biome clean. |
|
Verified both halves at One thing to tighten before merge, and it is the test rather than the code. The writer-path test at That matters because the read-side fallback exists for exactly one case: a file written by a pre-fix build. Nothing currently exercises that shape end-to-end. Please add one test that seeds the file directly with old-shape content (both windows present, no window stamps, snapshot Nothing else outstanding — with that test in place this is ready. |
Quota only refreshed for an account that served a request, so a fallback that had not been routed to in a while showed stale numbers - and those numbers are what routing and the killswitch decide on. An unref'd five-minute jittered poll now refreshes every account. A four-minute freshness gate skips accounts that recent traffic already covered, and the gate reads the shared sidebar file rather than local memory so N concurrent processes do not each pay for the same refresh. A cross-process lock with an auto-renewing lease keeps that to one refresher at a time. Snapshots merge per window by newest checkedAt instead of last-writer- wins, so an idle process cannot write its stale view over a fresher one. Each window carries its own stamp for that comparison, and a snapshot- level stamp remains as a fallback for files written before this change. Two tests cover the halves separately, because no single one can cover both: buildSidebarMachineState derives the per-window stamp and the snapshot stamp from the same value, so reverting the stamping leaves the fallback supplying an identical timestamp. The first seeds an old-shape file directly - both windows present, no per-window stamps - and asserts they survive a write carrying crossed stamps, which is the migration path the fallback exists for. The second pins why the stamping matters: the merged snapshot stamp is the max across both windows, so a winning window that lands unstamped beside a fresher-stamped sibling inherits that sibling's freshness on the next read and can beat a write that is genuinely newer than it.
4d443f0 to
4063e57
Compare
|
Pushed Your diagnosis of Why one test cannot red on either half alone. I tried two ways to break that redundancy before concluding it:
On (2). So Two tests, covering the halves separately:
Being straight about the limitation: the second test demonstrates the consequence of removing write-side stamping by constructing its output state — it does not gate the write-side hunk itself, and it reds on read-side revert rather than write-side. I would rather say that than let the pair look like it delivers the property you asked for. If you want the write-side genuinely gated, the change is in production rather than tests:
|
|
Verified at 751 tests pass locally on the branch. Ready to merge from my side. |
Why
Quota is push-primary (
x-codex-*headers / WScodex.rate_limitsframes), so a fallback account that is not currently being routed to shows stale or missing quota until the operator manually runs/openai-quota. The sibling anthropic-auth plugin keeps idle accounts fresh with a background poll; this brings the same behavior here — without request spam from multiple concurrent opencode processes, and without letting any process publish stale data over fresh.What
Background poll
core/background-quota-refresh.ts— singleton 5-minute timer (±30 s jitter per process), unref'd so it never keeps the process alive, idempotentstart, stopped from both the plugin-level and auth-loader dispose paths.refreshAllQuotawithrespectBackoff: trueplus a 4-minute freshness gate (skipFresherThanMs). Rides the 15 s abort timeout + quota-channel logging from fix(quota): bound and log the active quota fetch path #52. Deliberate deviation from the sibling: no immediate first run — the boot seed covers startup; first poll fires at the jittered 5-minute mark.Cross-process dedup (no locks)
checkedAt). Whichever process polls first refreshes the file; every other process's tick inside the window sees the freshcheckedAtand skips. Worst case under jitter collision is 2 concurrent fetches, never N.Stale-write clobber fix (machine-state sidebar writes)
setSidebarMachineStatenow freshness-merges per-account inside the existing compare-retry callback: for main and each fallback (matched by id), the side with the fresher finitecheckedAtwins; invalid/missing loses to valid; both missing → incoming wins.activeId/routekeep last-writer semantics;activeRoutingmerge unchanged.quota.checkedAtadded: a complete authoritative frame may retire every window (noprimary.checkedAtleft), and without a timestamp on the snapshot itself the freshness merge would resurrect the retired window from disk.Unchanged: the manual
/openai-quotacommand path (no gate, always writes) and the boot seed. No new config keys, commands, or TUI changes.Verification
random()=0/1→ ∓30 s.