Skip to content

OpenCode Go: /workspace/<id>/go was retired and now 302s to login — quota polling is permanently "opencode: unauthorized" #134

Description

@Diogenes50

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:

  1. 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"}).
  2. 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:

  1. 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?
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions