Repository navigation
fix(rate-limits): read OpenCode Go usage with the Go API key - #22514
kespineira wants to merge 4 commits into
Conversation
OpenCode's new Console sends accounts without a Black subscription away from the legacy console, so the cookie-based workspace lookup no longer returns usage. Read GET /zen/go/v1/usage with the OpenCode Go API key. The key comes from a new protected setting, then OPENCODE_API_KEY, then the key OpenCode saved with /connect in auth.json. The cookie path stays as the fallback for Black and legacy accounts. Fixes stablyai#22512
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe change adds protected storage and credential controls for the OpenCode Go API key. Rate-limit fetching can resolve keys from settings, the environment, or OpenCode’s local auth file, then request usage through a Bearer-authenticated endpoint or use the cookie fallback. The rate-limit service tracks whether a key is configured and updates status-bar provider visibility. Account settings include API-key save and clear controls, revised cookie and workspace guidance, search entries, and translations. Merge Risk: ⚪ Minimal · up to No material merge risk remains: a Zen-only key does not make OpenCode Go appear configured when its request is rejected. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description is detailed and covers the user impact, implementation, motivation, linked issue, testing, security, compatibility, and AI disclosure. However, the required Visual Proof section contains placeholder text instead of attached before-and-after screenshots or videos, and the related checklist item remains unchecked. Resolution Attach the required before-and-after screenshots or videos for the OpenCode Go status bar and Settings UI, then check the corresponding Visual Proof checklist item. If no visual change is intended, replace the section content with exactly
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: bb093996-bde1-4f44-af1e-31f298857063
📒 Files selected for processing (48)
src/main/persistence-protected-secret-fail-closed.test.tssrc/main/persistence/applying-settings/settings-update.tssrc/main/persistence/loading-store/loaded-state-parsing.tssrc/main/persistence/loading-store/state-serialization-secret-handling.tssrc/main/persistence/loading-store/state-write-round-trip.test.tssrc/main/protected-secret-persistence.tssrc/main/proxy-guarded-fetch-call-site-audit.test.tssrc/main/rate-limits/opencode-go-api-key.test.tssrc/main/rate-limits/opencode-go-api-key.tssrc/main/rate-limits/opencode-go-api-usage-fetcher.test.tssrc/main/rate-limits/opencode-go-api-usage-fetcher.tssrc/main/rate-limits/opencode-go-status-parsing.tssrc/main/rate-limits/opencode-go-usage-fetcher.tssrc/main/rate-limits/opencode-go-usage-parsing.test.tssrc/main/rate-limits/rate-limit-service-test-harness.tssrc/main/rate-limits/service-account-target-selection.test.tssrc/main/rate-limits/service-antigravity-usage.test.tssrc/main/rate-limits/service-inactive-account-previews.test.tssrc/main/rate-limits/service-live-claude-usage.test.tssrc/main/rate-limits/service-minimax-usage.test.tssrc/main/rate-limits/service-opencode-go-api-key.test.tssrc/main/rate-limits/service-refresh-orchestration.test.tssrc/main/rate-limits/service-window-activation.test.tssrc/main/rate-limits/service/service-configuration.tssrc/main/rate-limits/service/service-full-cycle-preparation.tssrc/main/rate-limits/service/service-state.tssrc/main/rate-limits/service/service-types.tssrc/main/startup/main-process-account-services.tssrc/renderer/src/components/settings/AccountsPane.test.tsxsrc/renderer/src/components/settings/AccountsPane.tsxsrc/renderer/src/components/settings/accounts-pane-provider-setting-sections.tsxsrc/renderer/src/components/settings/accounts-pane-types.tssrc/renderer/src/components/settings/accounts-search.test.tssrc/renderer/src/components/settings/accounts-search.tssrc/renderer/src/components/status-bar/status-bar-provider-visibility.test.tssrc/renderer/src/components/status-bar/status-bar-provider-visibility.tssrc/renderer/src/components/status-bar/use-status-bar-controller.tssrc/renderer/src/i18n/en-runtime-required.jsonsrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/shared/default-global-settings.tssrc/shared/global-settings-types.tssrc/shared/rate-limit-state-factory.tssrc/shared/rate-limit-types.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
There was a problem hiding this comment.
ℹ️ Minor suggestions only — the credential plumbing, persistence wiring, and upstream contract all check out.
Reviewed changes
- New API-key usage path:
opencode-go-api-key.tsresolves the Go key from the Settings override,OPENCODE_API_KEY, or OpenCode's localauth.json;opencode-go-api-usage-fetcher.tsreadsGET /zen/go/v1/usagewithAuthorization: Beareron the isolated Electron session. - Parser:
opencode-go-status-parsing.tsmapsusage.rolling/weekly/monthlyonto the session/weekly/monthly windows, clampingpercentto 0–100; session and weekly are required, monthly optional. - Routing and fallback:
opencode-go-usage-fetcher.tsprefers the key, falls back to the existing console cookie path on any error, and the service resets stale usage through a new SHA-256 config hash over source/key/cookie/workspace. - Persistence:
settings.opencodeGoApiKeyis a new protected-secret slot, wired through encrypt/decrypt, removal, and the fail-closed safeStorage paths. - Renderer: new Settings field, visibility through a published
opencodeGoApiKeyConfiguredflag, and the cookie/workspace fields relabeled as legacy fallbacks.
ℹ️ A credential change does not refresh the bar or discard an in-flight result until the next poll
Editing the new key field, or OpenCode rewriting auth.json, is only observed at the start of the next full cycle, and nothing enqueues one. If the change lands while a fetch is in flight, the previous credential's result is still applied, because the generation counter is only bumped inside the cycle that observes the change. MiniMax solves the analogous case with an explicit settings-change invalidation; OpenCode Go has no equivalent.
Technical details
# OpenCode Go credential changes lack immediate invalidation
## Affected sites
- src/main/rate-limits/service/service-full-cycle-preparation.ts:78-99 — key re-resolved and hash compared only at cycle start; the generation bump lives here.
- src/main/startup/main-process-account-services.ts:90-110 — `onSettingsChanged` invalidates MiniMax on endpoint/group/model changes but has no OpenCode Go branch and enqueues no refresh.
- src/main/rate-limits/service/service-full-cycle-application.ts:149,180-184 — an in-flight cycle applies its result whenever the generation still matches, which it does when no newer cycle has started.
## Required outcome
- After the user pastes or clears the key, the bar should refresh without waiting for the next scheduled poll.
- A result fetched under a credential that has since changed should not be applied.
## Suggested approach
- Mirror the MiniMax path: add an `opencodeGoApiKey` branch to the settings-change listener that invalidates the OpenCode Go snapshot and calls `refresh()`.
- This behavior is pre-existing for the cookie, so this is informational rather than blocking.DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…dential changes Move the OpenCode Go API key out of GlobalSettings into a main-owned encrypted file store behind write-only IPC, sharing the store with the MiniMax API key without changing its on-disk format. The renderer only learns whether a key is configured. Invalidate and refresh OpenCode Go usage after a key save or clear and after cookie or workspace changes, so a result fetched with an old credential is dropped. Treat an OPENCODE_API_KEY key that gets a 403 as not configured, since OpenCode Zen declares the same variable. Refs stablyai#22512
|
@pullfrog the credential-change note from your review is addressed in 30c8514. Saving or clearing the key, or changing the cookie or workspace ID, now runs |
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline. The credential-change invalidation from the prior review is addressed cleanly.
Reviewed changes
- Main-owned key store: the OpenCode Go key moved out of
GlobalSettingsinto an encrypted~/.orca/opencode-go-api-key.enc, backed by a new genericcreateEncryptedApiKeyFileStoreshared with MiniMax (same envelope format). - Write-only IPC:
opencodeGoCredentials:getStatus/saveApiKey/clearApiKeyexpose only{ apiKeyConfigured }; the web preload fails closed on save. - Credential-change invalidation: saving/clearing the key, or changing the cookie/workspace, now calls
invalidateOpenCodeGoCredentialState()(generation bump + snapshot clear) thenrefresh(), mirroring MiniMax; the prior review's concern. - Environment-key 403: a 403 for an
OPENCODE_API_KEY-sourced key maps tounavailable(bar hidden), while Settings/auth.jsonkeys stay visible errors. - Settings hygiene: unknown
opencodeGoApiKeyfields are stripped on load and update, and the field is removed fromPROTECTED_SECRET_SLOT/GlobalSettings.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one edge case in the credential fallback, inline.
Reviewed changes
- Key moved out of settings:
opencodeGoApiKeyis gone fromGlobalSettingsand the protected-secret slot. The key now lives in a main-owned encrypted file (encrypted-api-key-file-store.ts, shared with MiniMax with an unchanged on-disk format, plus the newopencode-go-api-key-store.ts), and the renderer only reads back{ apiKeyConfigured }through write-only IPC. - Immediate refresh and in-flight discard: saving or clearing the key, and changing the cookie or workspace, now bump
opencodeFetchGenerationand enqueuerefresh(), so a result fetched under the previous credential is dropped rather than applied. - Env-key 403 scoping: a 403 from an
OPENCODE_API_KEY-sourced key reads asunavailable(bar hidden, the prior thread addressed), while a Settings orauth.jsonkey stays a visible error and the cookie is still tried first. - Unreadable-key isolation: a stored key that fails to decrypt is confined to the OpenCode Go slot so it cannot abort other providers or leak credential detail, and the SHA-256 config hash no longer holds the plaintext cookie.
- Renderer, preload, tests: new write-only Settings field with a Saved/Not saved badge, cookie and workspace relabeled as legacy fallback, web preload fails closed, and new store/IPC/service/fetcher tests plus a shared-harness env guard.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
… or unreadable key Resolve OpenCode Go keys as an ordered candidate list. When an OPENCODE_API_KEY key gets a 403, try the distinct auth.json opencode-go key once before treating Go as not configured, so a Zen key in the environment no longer hides a Go key saved by /connect. Treat a saved key that cannot be decrypted as absent and keep the cookie and workspace, so the remaining sources still run. The decrypt error shows only when nothing else produced usage. Refs stablyai#22512
There was a problem hiding this comment.
ℹ️ No critical issues — one minor suggestion inline.
Reviewed changes
- Resolved OpenCode Go keys as an ordered candidate list, so an
OPENCODE_API_KEY403 retries a distinctauth.jsonopencode-gokey once before treating Go as not configured. - Split the stored-key resolver from the cookie/workspace resolver, so a key that fails to decrypt is treated as absent and the environment,
auth.jsonand cookie sources still run; the decrypt error is shown only when nothing else produced usage. - Extended the SHA-256 config hash to the full candidate list, so a change to either the environment or
auth.jsonkey discards stale usage. - Added fetcher- and service-level tests for the env-403→auth-file retry (200/403), no retry on 200/401/503, each fallback after a decrypt failure, and the equal-key dedupe.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Auth-file source preserved on an equal key:
resolveOpenCodeGoApiKeysnow pushes theenvironmentcandidate only when it differs from theauth.jsonopencode-gokey, and always pushes theauth-filecandidate. An equal key is sent once but classified asauth-file, so a 403 stays a visible error rather thanunavailable(bar hidden), and the redundant retry is correctly skipped. - Test updated: the dedupe test now asserts
[{ source: 'auth-file', key: 'fake-same' }](keeps the auth-file source once when it matches the environment key), and still asserts the distinct-key order is environment then auth-file.
This addresses the only open finding from the prior incremental review; no other files changed in this delta.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
I owe you an apology here: your PR came first and I didn't check for one before opening mine. You filed this at 17:54; I opened #22551 hours later and merged it. That's duplicated your work, and it was avoidable — I should have searched open PRs for #22512 before starting. Having now read yours side by side, your credential handling is better than what I merged, and I'd like to take it:
The one thing mine has that yours doesn't: a tier that reads OpenCode's Worth knowing before you rebase, from verifying on two real Windows hosts: the Concretely, I'd suggest: rebase this onto Sorry again for the overlap. |
|
No worries at all. #22551 got the fix out, and the credential-table tier is the part mine was missing. I took you up on the offer and ported the store onto main: #22630. It keeps your resolver, the credential-table tier and the 403 no-subscription state, and replaces the settings slot with the main-owned encrypted store and write-only One change to the precedence I'd like you to check. #22630 resolves the credential table before #22630 also migrates keys already saved under #22551 into the new store. The sealed value stays on disk, orcad included, until the new store holds the key, so a locked keychain at first launch doesn't lose it. Closing this one in favor of #22630. |

ELI5
Orca used to read your OpenCode Go usage by pretending to be your browser, using a cookie you copied from opencode.ai. OpenCode changed its website and that trick stopped working for most accounts. Now Orca asks OpenCode directly with your OpenCode Go API key, the same key OpenCode itself uses. You usually don't have to paste anything, because Orca finds the key OpenCode saved when you ran
/connect.What Changed
Before: the OpenCode Go bar needed a pasted opencode.ai Cookie header and a workspace lookup against the legacy console. Since 2026-09-22 that console sends accounts without a Black subscription to the login page, so the bar stays empty.
After: the bar shows 5h, weekly and 30-day usage from the API key. Orca picks the key in this order:
OPENCODE_API_KEYenvironment variable./connect($XDG_DATA_HOME/opencode/auth.jsonor~/.local/share/opencode/auth.json, entryopencode-go). Orca reads the file read-only, with a 1 MB cap, on each poll.Mechanism:
opencode-go-api-key.tsresolves the key.opencode-go-api-usage-fetcher.tscallsGET https://opencode.ai/zen/go/v1/usagewithAuthorization: Bearer. It uses the existing isolated Electron session, so Orca, environment and system proxies still apply. Cookies are omitted and redirects are refused.rolling,weeklyandmonthlypercentages onto the existing session, weekly and monthly windows.opencodeGoApiKeyConfiguredflag each cycle, the same way MiniMax and Grok do. The renderer shows the bar when only the key exists, and the key itself stays in main.opencodeGoCredentials:saveApiKey/clearApiKeyIPC and reads back only{ apiKeyConfigured }. After each save or clear, and after a cookie or workspace change, the service invalidates OpenCode Go and refreshes, so a result fetched with the old credential is dropped.OPENCODE_API_KEYis shared with OpenCode Zen. When an environment key gets a 403, Orca tries theauth.jsonopencode-gokey once. With no such key, the 403 means "not configured" and no bar is shown. A 403 for a Settings orauth.jsonkey stays a visible error.Why
The API key endpoint is what OpenCode's own server exposes for Go usage (
packages/console/app/src/routes/zen/go/v1/usage.tsin anomalyco/opencode). It needs no workspace ID, it doesn't depend on console internals like the server-function hash, and CodexBar already uses it.Alternatives I rejected:
Linked Issue
Fixes #22512
Visual Proof
Before: on v1.4.209 the OpenCode Go bar shows no usage for an account without Black.
After, with a fresh profile and nothing configured in Settings (key auto-detected from
auth.json):Settings > Accounts > OpenCode Go with the new API key field and the cookie relabeled as the legacy fallback:
Testing
On macOS I ran
pnpm devwithORCA_BACKGROUND_LAUNCH=1and an empty user-data profile, then drove it over CDP. The status bar and popover showed OpenCode Go 5h, weekly and 30-day windows matching a directcurlof/zen/go/v1/usagewith the same key. The weekly reset matched the API'sresetsAt.New tests:
opencode-go-api-key.test.ts: source precedence; missing, malformed and oversizedauth.json;XDG_DATA_HOME.opencode-go-api-usage-fetcher.test.ts: 200/401/403/5xx, transport errors, proxy settings, no key in errors.opencode-go-usage-parsing.test.ts: clamping, rate-limited windows, optional monthly.service-opencode-go-api-key.test.ts: key over cookie, cookie fallback, config-hash reset per source.Existing persistence, status-bar and settings tests were extended. The shared
RateLimitServicetest harness now mocks the key resolver, so no suite reads a developer's realOPENCODE_API_KEYorauth.json. The harness asserts that with a sentinel env key set.src/main/proxy-guarded-fetch-call-site-audit.test.tsnow lists the new fetcher. It uses the same isolated session and proxy path as the cookie fetcher.Local checks on macOS:
pnpm lintandpnpm typecheckpass.pnpm test: every suite this PR touches passes. Seven unrelated files also fail on a cleanmainat the same base commit (dac82f61bc):grok-replay-guard,claude-structured-real-cli,claude-tui-resume-real-binary.integration,pty-runtime-hidden-at-spawn-mark,opencode/hook-plugin-module-contract,e2e-worker-env-isolation.unitandrelay-region-correction.unit. So doesworktree-base-divergence-real-git, which times out. I checked each one onmain.pnpm build: every desktop, web-client and skills step passes. The mobile web bundle step can't run here becausemobile/dependencies aren't installed. This PR doesn't touchmobile/.Not tested: Windows and Linux were not run by hand. Path resolution follows OpenCode's
xdg-basedirusage, which is the same on every OS.AI Disclosure
I coordinated the work with Orca orchestration. Codex (
gpt-6-astra, xhigh) wrote the implementation, Claude (claude-fable-5-1) reviewed it, and Codex applied the review fixes. Claude Code (Opus) did the investigation, the endpoint verification and the manual testing. I reviewed the diff and re-ran the checks.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
src/main/credentials/encrypted-api-key-file-store.ts, shared with MiniMax without changing its format). It is not part ofGlobalSettings, sosettings:get,settings:changedand remotesettings.getnever carry it, and it is never logged or put in error strings.auth.jsondetection only reads the local machine and adds no remote reads.opencodeGoApiKeyConfiguredis an optional field, and a missing flag from an older host reads as not configured. The credential IPC is desktop-only, and the web client fails closed./connectinside WSL don't get auto-detection yet. Kimi's WSL home resolver is async and provider-specific, so reusing it needs polling changes. They can paste the key or setOPENCODE_API_KEYmeanwhile. I'll follow up separately.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred). The failures that remain also fail onmain, and the mobile bundle step needsmobile/deps. See Testing.