Skip to content

[Bug] OpenCode Go: rolling 5-hour usage shows 100% / "Exhausted Weekly" when opencode.ai reports 1% #247

Description

@makssiful

[Bug] OpenCode Go: rolling 5-hour usage shows 100% / "Exhausted Weekly" when opencode.ai reports 1%

Repo: nesszer/Win-CodexBar
Version: v0.46.0 (built 2026-07-30, Windows)
Provider: OpenCode Go (opencodego)
Severity: Medium — false "quota exhausted" state; actual usage is fine

Summary

When the OpenCode Go rolling (5-hour) usage is at exactly 1%, the menu bar shows the 5-hour bar at 100%, labels the provider "Exhausted Weekly", and the Pace widget reports "Far ahead (+79.6%) / Runs out in 0m" — even though the opencode.ai dashboard and the provider's own API both report 1%.

Weekly (15%) and monthly (4%) rows display correctly and match the server exactly, so the web fetch and cookie path are fine. Only the rolling window is wrong.

Expected vs actual

Window opencode.ai dashboard CodexBar (Win fork)
Rolling (5h) 1% 100%
Weekly 15% 15% ✅
Monthly 4% 4% ✅
Pace "Far ahead (+79.6%)", "Runs out in 0m" ❌ (derived from bogus 100%)

Reproduction

  1. Have an OpenCode Go subscription whose rolling 5-hour usage is exactly 1% (any value ≤ 1.0 in the API response).
  2. Run CodexBar v0.46.0 with the OpenCode Go provider enabled (web/Auto mode with a valid opencode.ai cookie).
  3. Observe the 5-hour bar at 100% red + "Exhausted Weekly" while opencode.ai shows 1%.

Evidence — live server payload

Authenticated fetch of https://opencode.ai/workspace/{wrk_id}/go (the exact URL the provider scrapes) returns:

rollingUsage:$R[34]={status:"ok",resetInSec:13631,usagePercent:1},
weeklyUsage:$R[35]={status:"ok",resetInSec:53863,usagePercent:15},
monthlyUsage:$R[36]={status:"ok",resetInSec:2334517,usagePercent:4}

usagePercent here is an integer percent (1 = 1%), matching the dashboard. The reset values also match the dashboard countdowns (3h47m / 14h58m / 27d0h at fetch time).

Root cause

rust/src/providers/opencodego/mod.rs, OpenCodeGoProvider::extract_window() (around lines 180–229):

let percent = super::extract_number(&percent_pattern, text);
if let Some(p) = percent {
    let reset = ...;
    // Regex path only matches direct percent field names — fraction
    // heuristic is safe here (upstream #2331). used/limit computed
    // percents must not use this path without a separate gate.
    let p = if (0.0..=1.0).contains(&p) {
        p * 100.0
    } else {
        p
    };
    return Some((p.clamp(0.0, 100.0), reset.max(0)));
}

The <= 1.0 → *100 "fraction heuristic" is applied to the direct usagePercent field on the regex path. The real server sends integer percent (usagePercent: 1 = 1%). The heuristic interprets 1 as the fraction 0.01 and multiplies by 100 → 100%.

  • Weekly 15 and monthly 4 are > 1.0, so they pass through untouched — which is exactly why only the rolling bar is wrong.
  • The Pace widget and "Exhausted Weekly" label are downstream artifacts of the fake 100% rolling value.

Upstream comparison (why this is a Win-fork regression)

In upstream steipete/CodexBar (Sources/CodexBarCore/Providers/OpenCodeGo/OpenCodeGoUsageFetcher.swift):

  • Regex path (parseSubscription, lines ~437–471): extracts usagePercent directly with no <= 1 scaling. Correctly shows 1%.
  • JSON path (parseWindow, line ~779): applies if percentIsDirect, resolvedPercent <= 1.0 { resolvedPercent *= 100 } — where fractions genuinely occur in the JSON shape (upstream tests use usagePercent: 0.25 → 25%).

The Win fork ported the JSON-path heuristic onto the regex path, where the server's serialized $R[...] payload sends integer percent. That mismatch is the bug.

Suggested fix

In extract_window() (regex path), do not scale a direct usagePercent-style field by the <= 1.0 heuristic — mirror upstream's regex path, which treats the value as-is. Keep the heuristic only for fields where the server actually sends fractions, or drop it entirely on this path since the observed $R[...] serialization always uses integer percent (0–100).

Minimal change sketch:

let p = p; // remove: let p = if (0.0..=1.0).contains(&p) { p * 100.0 } else { p };

(Keep the existing clamp to 0..100.)

Related issues (different failure modes, do not close)

This issue is distinct: the web path works, but a sub-1% integer rolling value is mis-scaled to 100%, producing a false "exhausted" state. PR #215 ("fix sub-1% usage units") fixed the computed used/limit branch but left the direct-field heuristic, so this survives in v0.46.0.

Metadata

Metadata

Assignees

No one assigned

    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