Skip to content

fix(ai): openrouter quota unlimited path publishes a negative spend — "$-5.00 spent" for usage_monthly below zero #1913

Closed

Description

@dezren39

fix(ai): openrouter quota unlimited path publishes a negative spend — "$-5.00 spent" for usage_monthly below zero

Verified 2026-11-06 on origin/main 529cf7fa05 (merge of PR #1908), in a laptop worktree (~/git/fleet/probe-2026-11-06-creator-2135, kept).

The defect

packages/ai/src/quota/providers/openrouter.ts, the unlimited path (limit === null, ~:126-132): the monthly window's only figure is usage_monthly, read via the shared toNumber — which accepts any finite number, including negatives — and formatted straight into the label:

usedPercent = null
valueLabel = `$${formatMoney(usageMonthly)} spent`

Nothing checks the sign. A negative usage_monthly — a spend figure that cannot exist — publishes as a successful quota read whose window claims the account has spent a negative amount of money. The provider's own neighbouring code already treats impossible figures as no data: a zero/negative limit returns the no-data contract (#1799, window-contract rule 4). The spend figure on the unlimited path is the one figure in this provider with no sign check at all.

Sibling parsers guard the same shape: turso's numNonNegative, opencode-go's toMicroCents (rejects < 0), and command-code's monthlyRemaining >= 0 gate (#1744) all read a negative amount as absent. OpenRouter's limited path is also safe by construction — its label derives from used = max(0, limit − limit_remaining) and its over-limit shape is pinned publish-truthfully by the quota suite ($120.00 / $100.00 at a clamped 100%). Only the unlimited path's raw spend escapes.

Runtime probe (laptop worktree, probe-antigravity.ts, kept in the worktree)

Driving the real provider.fetchQuota with a stubbed fetch (the test suite's own harness shape):

Why it matters

On the unlimited path this label is the provider's entire quota signal — there is no percent and no limit to cross-check it against. A negative spend is impossible wire data (a billing reset glitch, a credits/refund adjustment spelled as spend, a gateway mimicking the /key shape with a signed balance); publishing it as ok: true quota data teaches downstream consumers a figure that cannot be true, in the one shape (credits/spend balance) where this fleet has repeatedly chosen "bad data reads as absent" over fabrication.

Acceptance criteria

  • A negative usage_monthly on the unlimited path gets one explicit disposition — read as absent (so a payload with no other figure returns the existing "No quota data in response" contract) is the sibling-consistent choice; any other disposition is chosen and recorded in the closing note.
  • Positive and zero usage_monthly on the unlimited path are unchanged ($12.50 spent, $0.00 spent controls).
  • The limited path is unchanged, including its pinned publish-truthfully over-limit shape.
  • packages/ai/test/quota.test.ts pins the negative-spend case beside the existing fix(ai): openrouter quota publishes a zero-limit window — $0.00 / $0.00 with null percent reads as headroom where there is no limit #1799 openrouter describe.
  • Existing quota suite stays green.

Dedup

Searched open + closed issues for openrouter negative spend / usage_monthly this run: no covering issue. #1799 (closed) covered the zero/negative-limit no-data contract; #1725 (open) covers test pinning for opencode-go's parsers and resolveResetAt plus opencode-go's reset-null window drop — not this spend figure. The over-limit limited-path label is deliberately pinned publish-truthfully by the suite and is explicitly out of scope.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/aiPackage: ai (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 7, 2026
  2. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-opencode)
    • Session / ID: cron new-issue-creator-opencode, run 2026-11-07T03:44Z
    • Found via: filed this run
    • Task: Triage the openrouter unlimited-path negative-spend defect: a negative usage_monthly must not publish as a "$-5.00 spent" quota window — read it as absent (the sibling-consistent disposition) or record another chosen disposition.
    • Expectation: effort/s — done when a negative spend no longer publishes, positive/zero controls and the limited path (including its pinned over-limit shape) are unchanged, and quota.test.ts pins the negative case.
    • Links: probe probe-antigravity.ts in laptop worktree ~/git/fleet/probe-2026-11-06-creator-2135 (origin/main 529cf7f); body file bodies/opencode-openrouter-negative-monthly-spend.md in the goal hidden_files.
  3. developing-today commented on Nov 7, 2026

    @developing-today

    Picked up by repo-worker-opencode-a — working this now. (Fleet queue fallback mode: the queue backend is down, so this issue's comments carry the claim. The claim stays live while repo-worker-opencode-a posts progress markers; after 30 minutes of silence any other worker may pick this up.)

  4. added
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 7, 2026
  5. developing-today commented on Nov 7, 2026

    @developing-today

    Progress from repo-worker-opencode-a: triaged-confirmed at main 10f3058: openrouter.ts unlimited path formats usageMonthly into the label with no sign check (only a null check); routing task/small-fix

  6. developing-today commented on Nov 7, 2026

    @developing-today

    Triage — CONFIRMED (lane opencode-a, fallback mode)

    Verified fresh against origin/main @ 10f305865fe8 (laptop primary ~/git/opencode, clean, on main):

    • packages/ai/src/quota/providers/openrouter.ts: the unlimited branch (limit === null, ~lines 126–132) sets valueLabel = $${formatMoney(usageMonthly)} spent`` directly from toNumber(data.usage_monthly). The only guard on that figure is the null check just above it (`limit === null && usageMonthly === null` → no-data contract) — there is no sign check, and `toNumber` accepts any finite number, negatives included. A negative `usage_monthly` therefore publishes `$-5.00 spent` with `ok: true`, exactly as the filed probe shows.
    • The asymmetry the issue calls out is visible in the same function: a zero/negative limit returns the no-data contract under the fix(ai): openrouter quota publishes a zero-limit window — $0.00 / $0.00 with null percent reads as headroom where there is no limit #1799 comment block immediately above, and the limited path derives its label from Math.max(0, limit − limit_remaining) — the unlimited path's raw spend is the one unchecked figure, as filed.

    Routing: task/triage → task/small-fix, back to queue/queued with status/needs-triage. Sibling-consistent disposition (negative reads as absent → existing no-data contract) is named in the criteria as the expected choice; positive/zero controls and the limited path's pinned over-limit shape stay untouched. Tests pin the negative case beside the existing #1799 openrouter describe. No Drew decision needed.

    agent-note:v1 · agent: repo-queue-opencode-worker-a · lane: opencode-a · mode: fallback (queue backend down, code#6619)

  7. 5 remaining items

  8. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Picked up by repo-worker-opencode — working this now. (Fleet queue fallback mode: the queue backend is down, so this issue's comments carry the claim. The claim stays live while repo-worker-opencode posts progress markers; after 30 minutes of silence any other worker may pick this up.)

  9. added
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 7, 2026
  10. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Progress from repo-worker-opencode: verified: inspecting openrouter unlimited path + 1799 describe; next: worktree + sign guard

  11. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Progress from repo-worker-opencode: implementation_complete: negative spend read as absent, quota suite 133 pass, neg control fails stashed; next: PR + merge

  12. added a commit that references this issue on Nov 7, 2026
  13. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Released by repo-worker-opencode (outcome: done). fixed_merged: PR #1938 MERGED 59834b0 (branch queue/1913-1913-openrouter-negative-spend); negative usage_monthly read as absent -> no-data contract on unlimited path; quota.test.ts 133 pass, neg control fails stashed; issue CLOSED; backlog + sweep.log updated

  14. removed
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    on Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app/aiPackage: ai (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions