Skip to content

fix(opencode): read pay-as-you-go usage instead of failing with HTTP 500 - #2504

Merged
steipete merged 13 commits into
steipete:mainfrom
epoch-chrono:fix/opencode-pay-as-you-go-usage
Aug 14, 2026
Merged

fix(opencode): read pay-as-you-go usage instead of failing with HTTP 500#2504
steipete merged 13 commits into
steipete:mainfrom
epoch-chrono:fix/opencode-pay-as-you-go-usage

Conversation

@epoch-chrono

@epoch-chrono epoch-chrono commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Fixes the OpenCode API error: HTTP 500: HTTPError reported in #706 and #273. I traced it to a schema change on opencode.ai rather than a transient server fault, and it turns out to be fixable client-side — the usage data is still served, under different field names.

What breaks today

OpenCodeUsageFetcher resolves the workspace, then asks the subscription server function (7abeebee…) for usage. For a workspace that bills per request, that object no longer exists. I replayed the exact three calls the provider makes, with the same server IDs, headers and cookie:

1) GET  /_server?id=def3997…ba0234f                     -> 200  workspace resolved
2) GET  /_server?id=7abeebee…d691b4&args=["wrk_…"]      -> 200  payload is null
3) POST /_server  (X-Server-Id: 7abeebee…d691b4)        -> 500  {"status":500,"unhandled":true,"message":"HTTPError"}

Auth is fine (no 401/403) and the workspace lookup works — the failure is isolated to the subscription function. Two details matter:

  1. The account is pay-as-you-go, so its billing object has subscription: null, and the subscription function has nothing to return.
  2. rollingUsage / usagePercent no longer appear anywhere in the billing data (0 occurrences). The current fields are monthlyUsage, monthlyLimit and balance. So both the parseSubscription regex and the subscription call target a shape opencode.ai has replaced.

There is also a smaller issue: a server function that resolves to null answers with …["server-fn:<uuid>"]=[],null), which isExplicitNullPayload does not recognize. That is why the POST retry is sent at all, and it is the request that returns HTTP 500.

What this changes

When the subscription lookup fails in a subscription-shaped way, I fall back to the customer/billing server function (c83b78a6…) — the same one OpenCodeGoUsageFetcher already reads the Zen balance from — and derive usage from what opencode.ai serves today:

  • usedPercent = monthlyUsage / monthlyLimit, rendered as the primary window
  • monthlyUsage, monthlyLimit and the remaining prepaid balance, rendered as provider cost

Deliberately conservative:

  • Workspaces that still return a subscription keep the existing path untouched; the new code never runs for them.
  • The fallback only triggers on apiError / parseFailed. Credential and network failures propagate as before, and an expired session detected during the fallback still surfaces as invalidCredentials.
  • When the billing payload has no usage either, the existing "no subscription usage data" error is raised — the raw HTTP 500 is no longer what the user sees.
  • The billing object carries no cycle boundary, so resetsAt stays nil rather than guessing one.

On the unit scale: balance and monthlyUsage arrive as integers scaled by 1e8, while monthlyLimit / reloadAmount / reloadTrigger are whole USD. I did not pick that divisor myself — OpenCodeGoZenBalanceParser.billingScale already uses it for the balance this app renders today, and my live values are consistent with it (spend matches the cycle, and the balance sits above the configured auto-reload trigger, which had not fired). It is isolated in a single named constant.

Parsing is tolerant of both shapes: the billing function replies with SolidStart's $R[...] JavaScript payload, so I try JSON first and fall back to a field scan that requires customerID before trusting any number.

Tests

  • New OpenCodeZenBillingParserTests plus a redacted billing fixture (Tests/CodexBarTests/Fixtures/Providers/OpenCode/billing-pay-as-you-go.txt): $R[...] payload, JSON payload, missing limit, legacy workspace that still has a subscription, and payloads that must be rejected.
  • New cases in OpenCodeUsageFetcherErrorTests: pay-as-you-go workspace now yields a snapshot instead of an error, the POST that returns 500 is no longer sent, a POST failure still recovers through billing, and a signed-out billing response maps to invalidCredentials. The existing null-payload test now asserts the graceful error after the billing attempt.
  • New OpenCodeMenuCardCostTests for the menu card itself: a workspace with a limit renders the percentage, and one without a limit still renders spend and balance instead of an empty card.
  • New toUsageSnapshot cases in OpenCodeUsageParserTests for the monthly window, the cost snapshot, the no-limit case, and clamping above 100%.

Evidence from a real pay-as-you-go workspace

Captured today against a live opencode.ai account. Workspace/customer IDs are redacted; the session cookie was read from a file and never printed, and the account values below are my own.

Before — main (this PR reverted), same account, same cookie. A small executable linking CodexBarCore from main and calling OpenCodeUsageFetcher.fetchUsage directly:

codebase: steipete/CodexBar main (no fix)
error com.steipete.codexbar.opencode-usage: [CodexBarCore] OpenCode subscription payload missing after GET; retrying with POST.
error com.steipete.codexbar.opencode-usage: [CodexBarCore] OpenCode returned 500 (type=application/json;charset=UTF-8 length=53)
  result: OpenCode API error: HTTP 500: HTTPError

The three requests the provider makes, replayed at the HTTP level with the same server IDs and headers:

=== 1) workspace GET -> HTTP 200 (len=223) ===
((self.$R=self.$R||{})["server-fn:<uuid>"]=[],($R=>$R[0]=[$R[1]={id:"wrk_<redacted>",name:"<redacted>",slug:null}])(...))

=== 2) subscription GET -> HTTP 200 (len=93) ===
((self.$R=self.$R||{})["server-fn:<uuid>"]=[],null)

=== 3) subscription POST (retry) -> HTTP 500 (len=53) ===
{"status":500,"unhandled":true,"message":"HTTPError"}

Step 2 is the payload isExplicitNullPayload did not recognize, which is why step 3 is sent at all. Step 3 is the request that produces the reported error.

What the billing data actually contains for that same workspace, from the customer/billing server function this PR falls back to:

...reloadTrigger:5,reloadTriggerMin:5,monthlyLimit:20,monthlyUsage:1556267684,timeMonthlyUsageUpdated:...
...paymentMethodLast4:null,balance:1326177004,reload:!0,reloadAmount:10,reloadAmountMin:10,...

This is also the concrete check on the fixed-point semantics: monthlyUsage 1556267684 / 1e8 = $15.56 against a monthlyLimit of 20 already in whole USD, and balance 1326177004 / 1e8 = $13.26, which is above the reloadTrigger of 5 — consistent with auto-reload not having fired. rollingUsage / usagePercent appear zero times anywhere in this payload.

After — this branch, same account, same cookie, through the full fetch path (workspace lookup, subscription attempt, billing fallback, snapshot conversion):

case: live account (cookie loaded, not printed)
  ok   monthly spend $15.56 of $20.00 (77.8%), balance $13.26
  ok   primary window usedPercent 77.8%

So where the provider previously surfaced HTTP 500: HTTPError, it now reports the monthly spend, the limit and the remaining prepaid balance, and the values line up with the raw payload above.

One thing this proof does not cover: I could not run make test or launch the packaged app, since this machine has Command Line Tools but no full Xcode (the app target needs the #Preview macro plugin, and SwiftLint needs sourcekitd). The run above links CodexBarCore from this branch directly, so it exercises the fetch and snapshot path but not the SwiftUI rendering; the menu-card branch is covered by OpenCodeMenuCardCostTests instead. The CI run on this PR is still awaiting maintainer approval, so the suite has not executed yet.

Refs #706, #273

Fixes #2697

opencode.ai retired the payload the OpenCode provider parses. Workspaces that
bill per request have `subscription: null`, so the subscription server function
answers with an empty payload on GET and HTTP 500 on the POST retry, which
surfaces as "OpenCode API error: HTTP 500: HTTPError" and leaves the provider
without any usage to show. `rollingUsage.usagePercent` no longer appears in the
billing data at all.

I fall back to the customer/billing server function, the same one the OpenCode
Go provider already reads the Zen balance from, and derive usage from the fields
opencode.ai serves today: monthly spend against the configured monthly limit,
plus the remaining prepaid balance. Workspaces that still carry a subscription
keep the existing path untouched, and the fallback only runs for
subscription-shaped failures so credential errors still surface as such.

I also treat a server function that resolves to null as an explicit null
payload, so the POST retry that answers HTTP 500 is never sent.

Refs steipete#706, steipete#273

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d56479cf1a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
let cost = ProviderCostSnapshot(
used: usage.monthlyUsageUSD,
limit: usage.monthlyLimitUSD ?? 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render no-limit OpenCode pay-as-you-go spend

When the billing payload has monthlyLimit: null, this converts it to limit: 0 while primary is also nil, and the existing menu model drops OpenCode provider-cost sections with nonpositive limits (MenuCardView+Costs.swift:485). In that no-limit pay-as-you-go case the fetch now succeeds but the card still has no metric or cost section, so the parsed monthly spend/balance is effectively hidden; please add an OpenCode no-limit rendering path or otherwise keep the spend displayable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 5dd30e7.

You are right that the no-limit case fell through to guard cost.limit > 0 and left the card with nothing on it, even though the spend and balance had been parsed. I added an OpenCode branch for limit <= 0 in MenuCardView+Costs.swift, following the shape OpenAI and ClawRouter already use for limitless spend: monthly spend as the spend line, remaining prepaid balance underneath, no percentage. Workspaces that do have a limit keep the existing percentage rendering.

I kept limit: 0 as the signal rather than making the cost snapshot optional, since that is the convention those providers already rely on, and documented it where the snapshot is built. New OpenCodeMenuCardCostTests covers all three cases through Model.make: with a limit, without a limit, and without a limit or balance.

A pay-as-you-go workspace with no monthly limit produced an empty card: the
fetch succeeded, but the snapshot has no primary window (no limit means no
percentage) and its provider cost carries limit 0, which the shared cost
section drops. The monthly spend and the prepaid balance were parsed and then
never shown.

I add an OpenCode branch for that case, matching the one OpenAI and ClawRouter
already use for limitless spend: monthly spend as the spend line, remaining
balance underneath. Workspaces that do have a limit keep the existing
percentage rendering.
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels Jul 29, 2026
@clawsweeper

clawsweeper Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed August 13, 2026, 8:48 PM ET / August 14, 2026, 00:48 UTC.

ClawSweeper review

What this changes

The PR falls back from unavailable OpenCode subscription usage to pay-as-you-go billing data and renders monthly spend, an optional limit, and prepaid balance.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep this PR open: it remains the active implementation candidate for the linked OpenCode provider failure, with strong contributor and owner-supplied proof but an external billing-schema compatibility decision still requiring maintainer review.

Priority: P2
Reviewed head: 2a6c15af1d93ebba1df46d6683932e073650f8c8

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The patch has focused coverage and credible real-provider proof, with the remaining uncertainty limited to an undocumented upstream billing contract.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The PR body includes redacted before-and-after live output through the real OpenCode fetch and snapshot path, and owner follow-up records focused test validation.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The PR body includes redacted before-and-after live output through the real OpenCode fetch and snapshot path, and owner follow-up records focused test validation.
Evidence reviewed 4 items Provided PR diff: The proposed fetcher catches subscription-shaped API or parse failures and then reads the billing endpoint; the snapshot includes monthly spend, optional limit, and balance.
Fallback boundary coverage: The provided tests cover pay-as-you-go success, no POST retry after an explicit-null subscription response, transient subscription failure recovery, invalid credentials, and legacy subscription-account rejection.
Maintainer follow-up: The owner’s recorded follow-up added the transient subscription failure regression and preserved subscription-account classification before later branch refreshes.
Findings None None.
Security None None.

How this fits together

CodexBar’s OpenCode provider uses a session cookie to fetch provider usage, converts the response into a common usage snapshot, and displays that snapshot in the menu-bar card. This change adds a billing-data fallback when the subscription endpoint cannot represent a pay-as-you-go workspace.

flowchart LR
    A[Session cookie] --> B[OpenCode provider]
    B --> C[Subscription endpoint]
    C --> D{Usage available?}
    D -->|Yes| E[Usage snapshot]
    D -->|No| F[Billing endpoint]
    F --> E
    E --> G[Menu-bar usage card]
Loading

Before merge

  • Resolve merge risk (P2) - OpenCode’s authenticated billing response and its 1e8 fixed-point convention are undocumented external contracts; an upstream field or unit change could make the fallback unavailable or display incorrect costs despite parser rejection guards.
  • Resolve merge risk (P1) - The local checkout could not be inspected because the execution sandbox fails before commands run, so this review cannot independently audit the current head beyond the supplied GitHub diff and discussion.
  • Complete next step (P2) - This PR already owns the bounded implementation; a maintainer must decide whether its guarded use of OpenCode’s undocumented billing payload is acceptable.
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Changed surface 14 files affected; 789 additions, 6 deletions The change spans authenticated fetching, parsing, snapshot conversion, menu presentation, and focused regression coverage.

Merge-risk options

Maintainer options:

  1. Accept the guarded billing fallback (recommended)
    Merge the fallback with its existing field-presence checks and subscription-account rejection, accepting that OpenCode can change this undocumented authenticated payload.
  2. Pause for a supported upstream contract
    Keep the PR open if maintainers do not want CodexBar to derive billing from an undocumented OpenCode web response.

Technical review

Best possible solution:

Land the guarded billing fallback only after maintainer acceptance of the undocumented upstream billing contract, retaining the existing subscription path and graceful failure when required billing fields drift.

Do we have a high-confidence way to reproduce the issue?

Yes. The PR supplies a redacted live current-main trace using the same account and cookie, followed by an after-fix fetch-path result showing monthly spend, limit, and balance.

Is this the best way to solve the issue?

Yes, based on the supplied diff and proof: the fallback is restricted to subscription-shaped failures, preserves credential and normal subscription behavior, and degrades to an error rather than trusting missing billing fields.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against ec438d04c4c5.

Labels

Label justifications:

  • P2: The PR repairs a provider usage failure for OpenCode users without evidence of broad runtime impact.
  • merge-risk: 🚨 auth-provider: Merging changes how CodexBar interprets authenticated OpenCode endpoint failures and billing responses.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes redacted before-and-after live output through the real OpenCode fetch and snapshot path, and owner follow-up records focused test validation.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes redacted before-and-after live output through the real OpenCode fetch and snapshot path, and owner follow-up records focused test validation.

Evidence

What I checked:

  • Provided PR diff: The proposed fetcher catches subscription-shaped API or parse failures and then reads the billing endpoint; the snapshot includes monthly spend, optional limit, and balance. (Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift:104, 2a6c15af1d93)
  • Fallback boundary coverage: The provided tests cover pay-as-you-go success, no POST retry after an explicit-null subscription response, transient subscription failure recovery, invalid credentials, and legacy subscription-account rejection. (Tests/CodexBarTests/OpenCodeUsageFetcherErrorTests.swift:112, 2a6c15af1d93)
  • Maintainer follow-up: The owner’s recorded follow-up added the transient subscription failure regression and preserved subscription-account classification before later branch refreshes. (a33fdc7695b2)
  • Local inspection unavailable: Sandboxed commands fail before execution with bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted; source and history could not be independently inspected from this checkout.

Likely related people:

  • steipete: Recorded owner follow-up completed the fallback boundary and regression coverage, followed by multiple current-main merge refreshes on this PR branch. (role: recent area contributor; confidence: high; commits: a33fdc7695b2, cf70589df699, e67341be0ecd; files: Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift, Sources/CodexBarCore/Providers/OpenCode/OpenCodeProviderDescriptor.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Obtain maintainer acceptance of the guarded undocumented-billing fallback before merge.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (29 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-08T23:41:37.112Z sha 7d0379a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-09T01:44:16.161Z sha 7d0379a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-09T04:55:48.849Z sha 7d0379a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-12T10:09:17.445Z sha 7d0379a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-13T20:31:52.924Z sha cf70589 :: needs maintainer review before merge. :: none
  • reviewed 2026-08-13T21:10:18.325Z sha 0e52e3a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-13T21:56:23.039Z sha 5021e14 :: needs maintainer review before merge. :: none
  • reviewed 2026-08-13T23:29:27.927Z sha e67341b :: needs maintainer review before merge. :: none

@epoch-chrono

Copy link
Copy Markdown
Contributor Author

Fair ask — the PR body claimed a live verification without showing anything inspectable. I have now added the artifacts under "Evidence from a real pay-as-you-go workspace", captured today against a live account with IDs redacted.

It covers three things rather than just the after state:

  • Before: the same account and cookie against main with this PR reverted, returning OpenCode API error: HTTP 500: HTTPError, plus the two log lines that precede it.
  • The raw HTTP: the three requests the provider makes, showing the workspace GET at 200, the subscription GET returning the =[],null) payload, and the POST retry returning 500.
  • After: the same account through the full fetch path on this branch, reporting monthly spend, limit and remaining balance.

On the fixed-point concern specifically: the evidence includes the raw billing fields, so the arithmetic is checkable rather than asserted. monthlyUsage 1556267684 / 1e8 = $15.56 against a monthlyLimit of 20 already in whole USD, and balance 1326177004 / 1e8 = $13.26, which sits above the reloadTrigger of 5 — consistent with auto-reload not having fired. The divisor itself is not something I chose: OpenCodeGoZenBalanceParser.billingScale already uses 1e8 for the Zen balance this app renders today.

Two limits on the proof, stated plainly: it exercises CodexBarCore directly rather than the packaged app, because this machine has Command Line Tools but no full Xcode, so the SwiftUI rendering is covered by OpenCodeMenuCardCostTests instead of a screenshot. And CI on this PR is still awaiting maintainer approval, so the suite has not run yet.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 29, 2026
@epoch-chrono

Copy link
Copy Markdown
Contributor Author

On the remaining schema/unit risk, it may help to know how this degrades in practice, since the failure modes are not symmetric.

If a field is renamed or dropped, nothing is displayed wrong. OpenCodeZenBillingParser.parse requires customerID and monthlyUsage before it trusts any number; without them it returns nil, the fetcher logs "billing payload did not contain monthly usage fields" and rethrows the original subscription error, so the provider goes back to showing an error rather than a plausible-looking wrong figure. A missing monthlyLimit or balance degrades to nil individually: spend still renders, just without the percentage or the balance line.

If the unit convention changes, the realistic direction under-reports rather than over-reports. Moving monthlyUsage/balance to whole USD, or to any smaller scale, makes the 1e8 divisor produce roughly $0.00 — wrong, but obviously wrong to whoever is looking at the card. The only way to silently inflate the number would be opencode.ai moving to a larger scale than 1e8, which would be an unusual direction for a currency field.

Worth being explicit about the limits: the fixture tests pin today's shape, so they will keep passing if opencode.ai changes the payload upstream — the graceful nil path above is the actual protection, not the tests. And the fallback only runs after the subscription path has already failed, so workspaces that still return a subscription never reach this code. If you would rather have a defensive sanity check on the parsed magnitude before merge, I am happy to add one.

Merge current main, preserve subscription-account classification on fallback, and add regression coverage.
@steipete

steipete commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Completed the maintainer follow-up in a33fdc7695.

  • Merged current main (1a2aad7ee5) into the contributor branch without rewriting published commits.
  • Rejects the billing fallback when the billing payload still contains a subscription, preserving the original subscription API error instead of misclassifying the account as pay-as-you-go.
  • Adds the exact transient subscription-API failure regression.
  • Resolves the stale-base lint failures by separating the OpenCode fetcher's network and parsing extensions and extracting the pay-as-you-go cost helper.
  • Adds the 0.46.1 changelog entry with thanks to @epoch-chrono.
  • Preserves VISION.md exactly as it appears on current main.

The previous CI failures were stale-base issues: lint reported an 851-line OpenCodeUsageFetcher type body, while musl failed installing the Swift Static Linux SDK before either build step ran.

Proof on the pushed tree:

  • swift test --filter OpenCode — 130 tests passed.
  • make check — clean (SwiftFormat and SwiftLint: 0 violations).
  • make test — 779 selections across 65 groups; all 65 passed first attempt, with no retries or timeouts.
  • /Users/steipete/Projects/agent-skills/skills/autoreview/scripts/autoreview --mode commit --commit e7e5088417b08c12b0db3d374320d22a05dd805f — clean, no accepted/actionable findings. That review snapshot has the same PR tree as a33fdc7695 relative to current main.

No merge performed.

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Aug 3, 2026
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Aug 3, 2026
The only conflict was the OpenCode no-limit cost rule: main moved menu-card cost
presentation behind the provider descriptor, so the shared renderer now switches
on ProviderCostMenuCardStyle instead of provider identity, while this branch
still carried an OpenCode-specific helper in the renderer.

I resolved it by taking main's generic renderer and moving the rule to the seam
main established:

- Add a `payAsYouGoSpend` style. The existing styles do not cover this case:
  `payAsYouGoBalance` shows only a balance and `apiSpend` shows only spend, while
  a pay-as-you-go workspace with no configured limit needs monthly spend with the
  remaining prepaid balance next to it, and no percentage.
- Render that style generically, alongside the other balance styles.
- Select it from OpenCodeProviderDescriptor when the cost snapshot carries no
  limit, the same shape OpenAIAPIProviderDescriptor uses for `apiSpend`.

Workspaces that do report a monthly limit keep the generic budget rendering.
@epoch-chrono

Copy link
Copy Markdown
Contributor Author

Both author-side items are done in 7d0379ad7: the branch is merged with current main and the no-limit rule now goes through the descriptor seam.

Thanks for picking up the follow-up in a33fdc7695 — rejecting the fallback when the billing payload still carries a subscription is a better boundary than what I had, and the stale-base diagnosis explains the CI failures I could not reproduce.

The merge had a single conflict, and it was exactly the descriptor boundary: main moved cost presentation behind ProviderCostPresentation, while this branch still had openCodePayAsYouGoCostSection(provider:) in the shared renderer. I took main's generic renderer and moved the rule:

  • New payAsYouGoSpend style. I did not reuse an existing one because none fits: payAsYouGoBalance renders only a balance and apiSpend only a spend line, while a workspace with no configured limit needs monthly spend with the remaining prepaid balance beside it and no percentage.
  • The renderer switches on that style alongside the other balance styles, with no provider identity left in it.
  • OpenCodeProviderDescriptor selects it when the cost snapshot has no limit, mirroring how OpenAIAPIProviderDescriptor selects apiSpend.

Workspaces that do report a monthly limit keep the generic budget rendering, so that path is unchanged.

OpenCodeCostPresentationTests covers the presenter itself at model level: no limit, with a limit, and no cost snapshot at all. The existing OpenCodeMenuCardCostTests still exercises the full chain, since Model.make resolves the style from the descriptor.

Same caveat as before on my side: swift build --target CodexBarCore is clean and I verified the presenter against the real API through a harness linking CodexBarCore, but this machine has no full Xcode, so make check and make test need CI or your local run to confirm.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. labels Aug 7, 2026
@steipete
steipete merged commit cf7b67e into steipete:main Aug 14, 2026
9 checks passed
@epoch-chrono
epoch-chrono deleted the fix/opencode-pay-as-you-go-usage branch August 14, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenCode provider: HTTP 500/404 on _server with valid manual cookie

2 participants