Add /claude-prime: start 5h quota windows right after reset - #127
Add /claude-prime: start 5h quota windows right after reset#127iceteaSA wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 17 files
Architecture diagram
sequenceDiagram
participant User as User (TUI / Pi)
participant Cmd as Command Handler
participant PM as PrimeManager
participant Store as Account Storage (state + config)
participant QM as QuotaManager / Usage API
participant Claim as Claim Directory ($TMPDIR/prime/)
participant API as Anthropic Messages API
Note over User,API: COMMAND FLOW: /claude-prime on|off|status
User->>Cmd: /claude-prime on
Cmd->>Store: setPrimePersistentEnabled(true)
Cmd->>PM: start (unref'd 60s tick)
Cmd-->>User: Enabled status summary
User->>Cmd: /claude-prime off
Cmd->>Store: setPrimePersistentEnabled(false)
Cmd->>PM: stop (cancels any in-flight)
Cmd-->>User: Disabled status summary
User->>Cmd: /claude-prime status
Cmd->>Store: load stored counters + quotas
Cmd-->>User: Per-account status rows
Note over User,API: Pi is display-only (no toggle / manager)
Note over PM,API: PRIME MANAGER TICK (every 60s, unref'd)
loop For each OAuth account (main + enabled fallbacks)
PM->>PM: Check if opt-in enabled (re-reads config after each await)
alt Enabled and account eligible (OAuth, no permanent error, killswitch pass)
PM->>QM: Force-refresh account quota (with fresh access token)
Note over PM,QM: Capture pre-call checkedAt baseline
QM-->>PM: Quota snapshot + checkedAt + fresh flag
alt Snapshot is fresh (checkedAt > pre-call baseline)
PM->>PM: Compute nextDueAt = resetsAt + 60s
alt Current time >= nextDueAt (window should be primed)
PM->>Claim: try atomic claim: writeFile(accountId-epochMs, 'wx')
alt Claim acquired (wx succeeded)
PM->>PM: Build minimal request body (model haiku, max_tokens 1, "0")
PM->>API: POST /v1/messages with OAuth identity headers
Note over PM,API: No streaming, thinking, tools, or cache_control
API-->>PM: 200 OK + usage { input_tokens, output_tokens }
PM->>Store: incrementPrimeUsagePersistent(counters)
PM->>PM: Log success (info · prime)
else Claim already exists (wx threw EEXIST)
PM->>PM: Skip (another process already primed)
end
else Window not yet due
PM->>PM: Skip (too early)
end
else Snapshot is cached (checkedAt same as baseline)
Note over PM,PM: Freshness guard: quota backoff / already started
PM->>PM: Skip (window assumed already active)
end
else Not eligible
PM->>PM: Skip (log reason at debug/trace)
end
end
Note over PM,API: Claim markers swept after 6h by mtime
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 12 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
b62bfbf to
24dd9fa
Compare
|
This is a substantial feature and I am keeping it open, but it needs a dedicated design/rebase pass before merge rather than being treated as a routine command addition. The main correctness issue I found is marker identity. Bootstrap markers are named from The process-wide Before merge, please also:
The existing cross-process marker and quota-refresh tests are a good base, but these ownership boundaries need to be resolved first. |
ffc7934 to
6999696
Compare
|
Rebased onto v1.16.0 (sticky-balanced routing + CacheKeep changes resolved) and completed the design pass — squashed to a single commit ( Marker namespace / multi-config: all prime markers (fire markers and bootstrap sentinels) now live under Singleton ownership ( Reset-cycle test: a production-shaped test walks two full windows with injected clock/quota/send seams — expiry → exactly one fire at reset+60s (asserting the haiku request shape) → new window → no further fires until the next reset → second cycle fires again. Full gates green: opencode 891, core 113, pi 58, e2e 21, typecheck/lint clean. |
|
Thanks — the storage-path manager registry and cross-config namespace fix the ownership issue I raised, and the isolated local validation passed (1,062 unit tests, 21 E2E tests, and typecheck). I found four remaining blockers:
Once these are resolved I will rerun the complete gate. |
|
All four resolved in 1 — Claim identity includes the OAuth account. Markers now live under 2 — Usage counters are exact. 3 — Production request shape. 4 — Documentation. Full gates: opencode 891, core 116, pi 58, e2e 21, typecheck/lint clean. |
|
The counter locking, canonical request transformation, and documentation updates are good. One marker-identity blocker remains. The account fingerprint is derived from the refresh token:
Anthropic rotates refresh tokens during normal OAuth refresh. Therefore the same OAuth account receives a new marker directory after routine refresh (immediately for fallbacks, and for main after a process reload). Existing once-per-reset claims are no longer visible, so the same quota window can be primed again. I reproduced this directly with Please use a persistent, non-secret OAuth-account identity that survives access- and refresh-token rotation and changes only when the user actually replaces/re-logs the account. A generated auth-lineage ID persisted at login and carried across refresh is one option; an authoritative account UUID is another when reliably available. Add a regression that rotates the refresh token for the same account and proves exactly one send, plus a same-label re-login regression proving a genuinely replaced account gets a new identity. The documented PR #128 is now merged, so please rebase this update onto current |
f4ed53e to
7479fee
Compare
|
Fixed the marker-identity blocker and rebased onto current Persistent OAuth lineage identity. Markers are no longer keyed on the rotating refresh-token fingerprint. Each OAuth account carries an Your repro is covered red-first: same account, same storage path, same reset epoch, refresh-token rotated between ticks → exactly one Doc claim. Removed the fixed Rebased onto Gates green: typecheck · opencode 925 · core 116 · pi 58 · e2e 21 · lint clean. |
|
The fallback/re-login lineage fixes are good, but the main OpenCode OAuth account replacement case is still unresolved. I reproduced the remaining path directly against this head: {"hostAccount":"account-b","firstLineage":"16ffe43b-cf42-4c3d-9cf2-aee5b8a80245","secondLineage":"16ffe43b-cf42-4c3d-9cf2-aee5b8a80245","sameLineage":true,"sends":1,"refreshes":1}Runtime mechanism:
Documenting that a reconnect may wait until the next reset records the limitation, but does not make the marker account-scoped. Please derive or reconcile the main lineage against a stable account identity that survives token rotation but changes on actual account replacement. Claude bootstrap's account UUID looks like the strongest available identity; a persisted equivalent is also fine. Please add a regression that primes account A, replaces the main host credential with account B in the same sidecar, and proves B receives its own prime during the same reset window. The source/PR wording that still estimates roughly 20–21 input tokens should also be removed or replaced with measured live usage, since canonical request rewriting now adds the Claude Code identity/system blocks and actual accounting already comes from response usage. |
|
Fixed the main host-account replacement case and rebased onto current Main lineage reconciled against observed credential identity. The persisted main lineage now carries a bound refresh-token fingerprint:
Your reproduction is the regression: account A primes → the host credential is replaced with account B in the same sidecar → B mints a distinct lineage and receives its own prime during the same reset window ( Token estimate removed. The fixed "~20–21 input tokens" wording is gone from the source and all three READMEs — cost reporting now defers entirely to measured response usage, which the usage counters already record. Gates on the rebase: opencode 1019, core 128, pi 58, e2e 23 — all passing, typecheck/lint clean. |
Pull upstream PR cortexkit#127 (feat/prime) into main. /claude-prime is an opt-in (default off) command that fires a minimal claude-haiku-4-5 request ~1min after each account's 5h quota window resets, so the window starts on time instead of late. Conflicts resolved additively — kept both the Opus 5 recovery code (formatFallbackModelLabel, recoveryWarmChains) and the prime manager/formatters that landed adjacent in index.ts and sidebar-state.ts.
|
Rebased onto current |
|
Rebased onto current Gates on the rebased tree: typecheck, build, lint, biome check clean; opencode 1074 / core 139 / pi 62 all pass. One e2e test fails, and it is pre-existing on |
|
Rebased onto current Gates on the rebased tree: typecheck, build, lint, biome clean; opencode 1081 / core 139 / pi 62 pass. e2e 26 pass / 1 fail — the pre-existing |
|
Rebased onto current Reconciled with the injectable plugin timers from Gates on the rebased tree: typecheck, build, lint, biome clean; opencode 1086 / core 139 / pi 63 pass. e2e 26 pass / 1 fail — |
An OAuth account's five-hour quota window only starts counting when a request fires. After a reset, an idle account's window sits unstarted — the first real prompt starts it late, pushing every subsequent reset later.
/claude-prime(opt-in, default off) fires one minimal request per account ~1 minute after its 5h window resets, so the window starts immediately.What it does
five_hour.resetsAt.claude-haiku-4-5,max_tokens: 1, systemReply with 1 when you receive 0., user0— no streaming/thinking/tools/cache_control. ~20 input + 1 output tokens. Sent direct (not relayed) throughrewriteUrl()with full OAuth identity headers.writeFilewithwx) keyed<accountId>-<resetsAtEpochMs>under$TMPDIR/opencode-anthropic-auth/prime/, swept after 6h by mtime.checkedAtadvanced vs a pre-call baseline), so cached returns (429 backoff, cross-process quota-lock) can never fire against an already-started window. A futureresetsAtrecords "window active" and skips without claiming.count,inputTokens,outputTokens,since) in runtime state (never in config); cost estimate derived at display time from Haiku 4.5 pricing constants.Surfaces
/claude-prime on|off|status— registered command + TUI modal (Enable / Disable / Status / Back; Status shows per-account rows: next prime time, primed ✓, window active, error), Pi display-only.Primerow (next-due / primed / error tone); nothing in the collapsed view. Toggling publishes sidebar state immediately.primelog channel: fire successinfo, fire/token-refresh failureswarn(distinct events), skipsdebug, lifecycletrace; setting changesinfooncommands. Payloads carry labels and values only — never tokens or bodies.Lifecycle
PrimeManager(core) mirrorsCacheKeepManager: unref'd 60s tick, idempotent start/stop, plugin-level singleton guard (a reloaded plugin stops the previous instance), and the persisted opt-in + stop state are re-checked after every await boundary so/claude-prime offprevents any in-flight fire.Known bounded limitation, documented in-code: cross-process counter increments use the in-process save mutex, so two processes priming different accounts in the same tick can lose one cosmetic counter increment. Auth data is unaffected.
Verification
opencode -ssession against an isolated state dir.Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Starts each OAuth account’s five-hour quota window immediately after reset by sending one minimal
claude-haiku-4-5request, opt-in via/claude-prime. Previously windows started on the first real request; now they start ~60s post-reset to keep reset times aligned with minimal cost.wxmarkers under$TMPDIR/opencode-anthropic-auth/prime/<storage-fp>/<auth-lineage>/, swept after 6h, with catch-up on boot.@opencodeadopts a single manager per storage fingerprint and evicts on path change.{ quota, fetched }refresh result; includes a 5‑min throttle and a killswitch check that matches the Haiku-scoped window by display name whenmodelIdis absent.CLAUDE_HAIKU_4_5_PRICING(exportsCLAUDE_HAIKU_4_5_MODEL_IDandCLAUDE_HAIKU_4_5_PRICING).@opencodeadds/claude-prime on|off|statusplus a sidebar section;@piexposes Status only./claude-prime on.Written for commit f525303. Summary will update on new commits.
Greptile Summary
This PR adds opt-in priming for five-hour OAuth quota windows. The main changes are:
Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Sequence Diagram
sequenceDiagram participant PM as PrimeManager participant QM as QuotaManager participant KS as Killswitch participant Marker as Claim marker participant API as Anthropic API PM->>QM: Force quota refresh QM-->>PM: Quota and fetched flag alt Cached result PM-->>PM: Skip this tick else Fresh result PM->>KS: Check Haiku quota policy alt Blocked or window active PM-->>PM: Skip without claim else Eligible and due PM->>Marker: Atomically claim reset alt Claim acquired PM->>API: Send minimal request else Already claimed PM-->>PM: Skip duplicate request end end endReviews (18): Last reviewed commit: "feat: add /claude-prime — start 5h quota..." | Re-trigger Greptile
Context used: