Summary
OpenCode Go tracking is broken for every user on v2.14.3: opencode.ai has retired the server-rendered /workspace/<id>/go page that OpenCodeClient scrapes. The URL now answers 302 to the login flow for any cookie, and because fetchDashboardHTML maps every 3xx to ErrOpenCodeUnauthorized, the daemon logs an authentication failure forever:
level=ERROR msg="Failed to fetch OpenCode quotas" error="opencode: unauthorized"
The dashboard keeps rendering the last successful snapshot, so it looks alive — only the countdown timers move. In my case polling silently stopped on 2026-09-18 and the card still showed Reset at Sep 19 three days later.
Reproduction
onwatch v2.14.3, Windows amd64, OpenCode Go subscription, valid browser session.
Same UA and cookie handling as internal/api/opencode_client.go:
| Request |
Cookie |
Result |
GET /workspace/<wrk_id>/go |
auth=Fe26.2**… |
302 → https://opencode.ai/console/login |
GET /workspace/<wrk_id>/go |
valid __Host-console_session |
302 → /auth/authorize |
GET /workspace/<wrk_id>/go |
both |
302 → https://opencode.ai/console/login |
GET /console/api/go/status + x-org-id: <wrk_id> |
valid __Host-console_session |
200 with JSON quotas |
Two related findings:
- The console is now a SPA; quota data comes from
GET /console/api/go/status, which requires the x-org-id header (without it: 400 {"_tag":"OrgRequired","message":"x-org-id is required"}).
- Authentication now rides on
__Host-console_session, not the auth iron cookie. A stale auth=Fe26.2**… returns 401 on every console API route, so the setting hint ("The auth cookie value required for scraping the dashboard") points users at a cookie that no longer authenticates.
Response shape
{
"access": {
"endsAt": "2026-10-13T01:31:34.000Z",
"meters": {
"fiveHour": { "startsAt": "...", "resetsAt": "2026-09-22T05:24:12.585Z",
"limitMicroCents": "1200000000", "usedMicroCents": "18384251" },
"week": { "startsAt": "...", "resetsAt": "2026-09-28T00:00:00.000Z",
"limitMicroCents": "3000000000", "usedMicroCents": "767560314" },
"month": { "limitMicroCents": "6000000000", "usedMicroCents": "1796934324" }
}
}
}
month carries no resetsAt; access.endsAt is the correct substitute. Micro-cent fields are JSON strings.
Patch I am running locally
In internal/api/opencode_client.go:
- added
openCodeGoStatusPath = "/console/api/go/status" and fetchGoStatusJSON, which sends the existing openCodeAuthCookieHeader(authCookie) plus x-org-id: <workspaceID> and Accept: application/json;
- added
fetchGoStatusQuotas, mapping fiveHour/week/month to the existing five_hour/weekly/monthly quota names with utilization = usedMicroCents / limitMicroCents * 100, ResetsAt from the meter (falling back to access.endsAt);
FetchSnapshot tries the JSON API first and falls back to the old HTML scrape for anything that is not 401/403/context cancellation, so parseOpenCodeQuotas and its tests stay intact.
Result after restarting the daemon (values match the web console's 2% / 26% / 30%):
level=INFO msg="OpenCode poll complete" plan_name="OpenCode Go" quota_count=3
five_hour 1.53% resets=2026-09-22T05:24:12Z
weekly 25.59% resets=2026-09-28T00:00:00Z
monthly 29.95% resets=2026-10-13T01:31:34Z
Happy to open a PR with that change if the approach looks right. Two design questions:
- Should the
auth_cookie setting accept a full cookie header (it already does, via the auth= prefix check) and should the hint be reworded to say __Host-console_session is what authenticates now?
- Since the JSON API exposes real currency limits, would you rather store
currency-format quotas (OpenCodeQuotaFormatCurrency, micro-cents → USD) instead of the percentages the scrape produced? I kept percentages to avoid changing stored history.
Two smaller things noticed while debugging
- Changing the OpenCode auth cookie in the UI does not affect the running poller —
OpenCodeAgent holds the startup cfg, so a daemon restart is required. The settings UI gives no hint of that, and the log line Provider settings updated providers="[global opencode]" suggests the change took effect.
- On Windows the test binary for
./internal/api cannot build at v2.14.3, independent of this change: extra_coverage_test.go:1779 (and 1788, 2362, 3109) reference getCredentialsFilePath, which is not defined for GOOS=windows.
Environment: onwatch v2.14.3 (windows-amd64), Go 1.27.1, Windows 11.
Summary
OpenCode Go tracking is broken for every user on v2.14.3:
opencode.aihas retired the server-rendered/workspace/<id>/gopage thatOpenCodeClientscrapes. The URL now answers302to the login flow for any cookie, and becausefetchDashboardHTMLmaps every3xxtoErrOpenCodeUnauthorized, the daemon logs an authentication failure forever:The dashboard keeps rendering the last successful snapshot, so it looks alive — only the countdown timers move. In my case polling silently stopped on 2026-09-18 and the card still showed
Reset at Sep 19three days later.Reproduction
onwatchv2.14.3, Windows amd64, OpenCode Go subscription, valid browser session.Same UA and cookie handling as
internal/api/opencode_client.go:GET /workspace/<wrk_id>/goauth=Fe26.2**…302→https://opencode.ai/console/loginGET /workspace/<wrk_id>/go__Host-console_session302→/auth/authorizeGET /workspace/<wrk_id>/go302→https://opencode.ai/console/loginGET /console/api/go/status+x-org-id: <wrk_id>__Host-console_session200with JSON quotasTwo related findings:
GET /console/api/go/status, which requires thex-org-idheader (without it:400 {"_tag":"OrgRequired","message":"x-org-id is required"}).__Host-console_session, not theauthiron cookie. A staleauth=Fe26.2**…returns401on every console API route, so the setting hint ("The auth cookie value required for scraping the dashboard") points users at a cookie that no longer authenticates.Response shape
{ "access": { "endsAt": "2026-10-13T01:31:34.000Z", "meters": { "fiveHour": { "startsAt": "...", "resetsAt": "2026-09-22T05:24:12.585Z", "limitMicroCents": "1200000000", "usedMicroCents": "18384251" }, "week": { "startsAt": "...", "resetsAt": "2026-09-28T00:00:00.000Z", "limitMicroCents": "3000000000", "usedMicroCents": "767560314" }, "month": { "limitMicroCents": "6000000000", "usedMicroCents": "1796934324" } } } }monthcarries noresetsAt;access.endsAtis the correct substitute. Micro-cent fields are JSON strings.Patch I am running locally
In
internal/api/opencode_client.go:openCodeGoStatusPath = "/console/api/go/status"andfetchGoStatusJSON, which sends the existingopenCodeAuthCookieHeader(authCookie)plusx-org-id: <workspaceID>andAccept: application/json;fetchGoStatusQuotas, mappingfiveHour/week/monthto the existingfive_hour/weekly/monthlyquota names withutilization = usedMicroCents / limitMicroCents * 100,ResetsAtfrom the meter (falling back toaccess.endsAt);FetchSnapshottries the JSON API first and falls back to the old HTML scrape for anything that is not401/403/context cancellation, soparseOpenCodeQuotasand its tests stay intact.Result after restarting the daemon (values match the web console's 2% / 26% / 30%):
Happy to open a PR with that change if the approach looks right. Two design questions:
auth_cookiesetting accept a full cookie header (it already does, via theauth=prefix check) and should the hint be reworded to say__Host-console_sessionis what authenticates now?currency-format quotas (OpenCodeQuotaFormatCurrency, micro-cents → USD) instead of the percentages the scrape produced? I kept percentages to avoid changing stored history.Two smaller things noticed while debugging
OpenCodeAgentholds the startupcfg, so a daemon restart is required. The settings UI gives no hint of that, and the log lineProvider settings updated providers="[global opencode]"suggests the change took effect../internal/apicannot build at v2.14.3, independent of this change:extra_coverage_test.go:1779(and 1788, 2362, 3109) referencegetCredentialsFilePath, which is not defined forGOOS=windows.Environment: onwatch v2.14.3 (windows-amd64), Go 1.27.1, Windows 11.