Conversation
… regex, improve error handling
The collector was falsely reporting 'Sign-in expired' for valid sessions.
This was caused by multiple interacting bugs:
1. Cookie selection was broken:
- The expiry filter compared milliseconds (Firefox storage) against
seconds (strftime('%s')), making it useless.
- We picked the single 'auth' cookie with the highest expiry, but
opencode.ai's Iron-encrypted cookies don't correlate browser expiry
with session freshness.
- We only sent 'auth=...', missing 'oc_locale' and other cookies the
server might need.
- We included subdomain cookies (auth.opencode.ai) in requests to the
main domain, which browsers don't do.
Fixed by replacing extract_auth_cookie with extract_all_cookies, which
reads every opencode.ai cookie ordered by lastAccessed (when Firefox
last sent it). build_cookie_header formats them exactly like a browser
would: all domain-matched cookies in one header, deduplicated by name
with the most recently accessed value winning. Subdomain-scoped
cookies are excluded to match real browser behavior.
2. The meter regex failed on the current opencode.ai page format:
The server now sends extra fields (usage, limit) inside meter objects.
The old regex expected usagePercent to be immediately followed by }.
Added [^}]* to tolerate any trailing fields.
3. Every HTTP error was called 'Sign-in expired':
404, 429, 500, etc. all produced the same misleading message.
Added FetchError with the real status code; only 401/403 get the auth
label. Everything else gets 'Server error' with retryAdvised.
4. Added retry logic for transient failures:
fetch_page retries once on transport errors (timeout, no route) and
on 5xx responses, with a 1-second pause. 4xx errors are not retried.
5. Added format-change detection:
When the Go page loads but carries no meter data, we now check for
'OpenAuth' or '/signin' to distinguish a genuine auth redirect from
an unexpected page layout. The latter gets 'Page format changed'
with retryAdvised, making future breakages easier to diagnose.
6. Added --debug flag for diagnostics.
7. Hardened cookie DB access:
OSError/PermissionError during the sqlite copy are caught and treated
as a missing cookie instead of crashing the collector.
Tests updated for the new cookie API, meter fixture format, and format-
change detection path.
The usage page SSR embeds only the newest 50 per-request records and the SPA fetches older pages through its usage-list server function. The collector stopped at that page, so any day that scrolled past the newest 50 vanished from the record on the next refresh: yesterday read 0 the moment today filled the page. Walk the server-function pages newest-first until a page comes back empty or its oldest record predates the trailing 7 local days, then bound the per-request stats (today*, recentDays, modelUsage) to that window. Page size is not a stop signal — records land while the walk runs and an interior page can come back short — and the walk keeps the pages already parsed if one fails mid-way.
The collector silently dropped every meter whose status was not 'ok'. A meter flipped to 'rate-limited' is the binding constraint, not a gap: the panel went quiet exactly when requests stopped. Now: - build_limits keeps every reported meter, clamping the percentage at full and carrying the server's status on throttled entries. - the record announces the exhausted window in usageStatusText and explains the consequence in authHelpText: requests block until spend ages out, or — when the workspace's 'Use balance' option is on and Zen credits remain — the overage bills the Zen balance, with the amount left (balance field, 1e-8-dollar units like the meter limits). - parse_go_config reads useBalance and the preceding balance off the go page; an absent block parses as defaults (hard block).
Code review of the earlier provider work surfaced a dozen items; all are addressed here: - --limits-only now skips the usage walk (the panel's refreshLimits() call opens with a cheap limits-only refresh instead of paging the history). - build_limits emits an explicit title per window so the panel stops rendering the rolling 5-hour allowance as a generic session window. - the usage walk gets a 60-second wall-clock deadline on top of its page cap, and a page that refuses to parse stops the walk while keeping the pages already read (previously one bad record discarded everything). - number() maps non-finite values (inf, 1e999) to zero instead of raising OverflowError and aborting the parse. - cookie de-duplication sorts merged databases by lastAccessed so the freshest value wins across profiles, not whichever DB sorts first. - parse_go_config bounds its balance search to the enclosing workspace object instead of the whole page prefix. - write_limits_cache cleans up its temp file on the way out without swallowing KeyboardInterrupt/SystemExit, and limit_window_open keeps a cached window through its reset instant instead of dropping it exactly at reset time. - the fetch retry ladder is deduplicated into one _request_with_retries, and Panel.qml's formatDuration gains the compact form that used to live in a duplicated formatAge. Tests: collector suite grows to 22 (titles, limits-only walk skip, bounded balance search, cache-write path, profile discovery + cross-profile freshness, unparsable-page walk, non-finite counts).
Honoring --limits-only (skip the usage walk) had a side effect: the record it printed carried zeroed stats, and the update runner writes that record verbatim to the state file, so every panel open blanked the opencode-go tab — today's line, the week chart, and the model rows — until the next full refresh (up to 15 minutes). The sibling collectors never do this: Claude and Codex reuse a recent scan under --limits-only, and Fireworks has no separate walk to skip. main() now reuses the stats from the record the last run left in the state file when the walk is skipped (previous_stats), merging them with the fresh meters — the chart keeps the last numbers instead of zeroing out, and the walk still never runs. A record that is missing, foreign, or partial falls back to the zero shape exactly as if no run had written yet, and a full refresh still tallies stats from the walk as before. Tests: limits-only with no prior record still yields zero-shaped stats (hermetic XDG_STATE_HOME pinned so the suite never reads a real user's state), and a limits-only run with a prior stats-bearing record keeps today's counts, the chart, and the model rows while refreshing the meters.
The collector only read Firefox/Zen cookie stores, whose sqlite keeps
values in plaintext. Chrome-family profiles (Chrome, Chromium, Brave,
Edge, Vivaldi, Opera \u2014 classic ~/.config, Flatpak, and snap layouts)
encrypt them, so signing in with a Chromium-based browser never produced a
session. Now:
- find_chrome_cookie_dbs discovers every profile's Cookies database
(top-level or Network/Cookies) for all known Chrome-family config dirs.
- extract_chrome_cookies mirrors the Firefox reader: WAL-safe temp copy,
host scoping to the main opencode.ai domain, (name, value, lastAccessed)
tuples, and FILETIME timestamps normalized to Unix microseconds so the
cross-browser freshness sort stays honest.
- Values are deciphered with the browser's own SafeStorage password from
the Secret Service (secret-tool lookup by the application attribute), the
same item the browser reads: PBKDF2-HMAC-SHA1("saltysalt", 1) key,
AES-128-CBC over the b"v11" blob with its fixed all-space IV, the
SHA256(host_key) integrity prefix stripped as the store does. The legacy
b"v10" random-IV layout and the keyringless "peanuts" (and empty)
fallback keys cover older builds. App-bound b"v20+" rows, rows with no
candidate key, and stores on machines without the cryptography wheel or
secret-tool are skipped, never fatal; the password is only fetched when a
store actually holds encrypted opencode.ai rows.
Live-validated against the machine's own Chromium store (auth token
deciphers to its Django-signed value, oc_locale to "en"; the collector
authenticates and fetches meters through the Chrome cookie), with new
tests for v11 and v10 decryption, plaintext rows, subdomain scoping,
app-bound skipping, FILETIME normalization, profile discovery, and the
cross-browser freshest-wins merge.
- Panel section: the hero now names the Go plan, exhausted rate-limit windows repeat in the status card like auth failures, and a record that outlives a refresh cycle earns a stale detail pill. - Collectors table: opencode-go limits note rate-limited windows are reported at full with a status card. - OpenCode Go section: the glued-in Chrome sentence is restructured into a cookie-source breakdown (Firefox/Zen plaintext, Chrome-family SafeStorage with the v11/v10 layouts and the peanuts fallback, app-bound v20+ and missing-cryptography machines skipped), plus the facts that the most recently used browser's session wins when several hold one, and that opening the panel is a limits-only refresh that keeps the previous record's stats on the chart. - The account-scope paragraph now names OpenCode Go alongside Fireworks' billing API as an account-global stats source.
The limits bullet named only the session and weekly windows the claude and codex collectors report; the branch adds a monthly window via the opencode-go meter. The providers settings example also predated the opencode-go manifest default.
Member
|
Automated duplication check: this pull request looks similar to #8065, which covers the same OpenCode usage collector. I keep that one open and close this one to consolidate review. If you feel this is the wrong decision, please open the PR again with a note on the difference. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the OpenCode Go provider to the agents panel: a new collector (
omarchy-agent-usage-opencode-go) plus the small panel-side plumbing it needs (stale-data marker, assets, manifest entry, docs).OpenCode Go exposes no usage API, so the collector reads the opencode.ai workspace console, using the
authsession cookie any browser on the machine stores for opencode.ai:/workspace/<id>/go. Meters the console has flipped torate-limitedare reported at full percentage with their reset countdown (each window travels with an explicitRolling/Weekly/Monthlytitle), and the status card explains the consequence: requests block until spend ages out, or — with the workspace's "Use balance" option on and Zen credits remaining — the overage bills the balance, with the amount left.v11fixed-IV layout and the legacyv10layout, with the keyringless "peanuts" fallback. App-bound (v20+) rows, locked keyrings, and machines withoutcryptography/secret-toolare skipped, never fatal; when several browsers hold a session, the one used most recently wins.Notes for reviewers
WORKSPACES_SERVER_FN_ID,USAGE_SERVER_FN_ID) and the seroval argument encoding are pinned to the current opencode.ai SPA; a console redesign needs those constants refreshed, as documented in the collector.scope: "account", so cross-device sync aggregation takes the widest value rather than summing the same account twice.workspaceIdin~/.config/omarchy/agents/opencode-go.jsonorOPENCODE_WORKSPACE).Tests
agents-panel-test.sh(11) andagent-usage-update-test.sh(7) extended and passing;./test/cligreen.