Skip to content

fix(ws): mark and reroute on admission-time usage limit#53

Open
iceteaSA wants to merge 1 commit into
cortexkit:mainfrom
iceteaSA:fix/ws-admission-rate-limit
Open

fix(ws): mark and reroute on admission-time usage limit#53
iceteaSA wants to merge 1 commit into
cortexkit:mainfrom
iceteaSA:fix/ws-admission-rate-limit

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

The mid-stream reroute only fires on a response.failed frame carrying rate_limit_reached_type — genuine mid-generation exhaustion. But when an account is already at 100% at admission, Codex rejects the turn with a different shape: an in-band type:error frame whose body is

{"error":{"type":"usage_limit_reached","message":"The usage limit has been reached","plan_type":"team","resets_at":1784958366,"resets_in_seconds":514504}}

(or, on the raw-WS transport, an HTTP-429 upgrade failure). Neither surface set a rate-limit mark, so OpenCode's retry loop re-issued the request, the fetch override still saw the account as healthy, routed main-first back to the exhausted account, and looped until the retry budget died in a user-facing "Retry Error" modal. Live log signature: WS transport + codex.rate_limits pairs seconds apart with usedPercent:100, zero mark lines.

Changes:

  • In-band type:error admission failures carrying usage_limit_reached (or status 429) now mark the connection's captured account before rejecting the body with the retryable ResponseStreamError, so the re-issued request reroutes same-turn. Attribution uses the send-time per-connection identity, same as onQuota.
  • The body's resets_at is used as the authoritative mark expiry when present; the bounded default stays the fallback.
  • Raw-WS upgrade failures now retain status/headers/body (both Bun and node clients). A rejected upgrade whose socket closes with a truncated body finalizes the structured error once instead of degrading to a bare close — that path previously returned undefined and lost the 429 classification entirely.
  • Negative classification is pinned by tests: a 503 circuit-open body, an untyped statusless error, and websocket_connection_limit_reached (HTTP-fallback path) do not mark.
  • The response.failed mid-stream branch and the no-replay gate (emitted) are unchanged.

Red-first throughout (admission body-rejection contract, index-level reroute, reset math, truncated-upgrade finalization). Gate: build, tsc, 686/686, biome clean. Two independent reviews: MiniMax-M3 (approve, 0 must — its should surfaced the truncated-upgrade defect) and GLM-5.2 (approve, 0 must / 0 should — probe-refuted its own dead-code hypothesis, confirmed send-time attribution and single-emit finalization).


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes WebSocket admission-time rate limits by marking the sending account with the provider’s reset and returning a retryable error, so the next attempt routes to a healthy account. Also preserves 429 upgrade details and finalizes truncated bodies to keep classification correct and prevent the retry loop.

  • Bug Fixes

    • Admission errors (type:error with usage_limit_reached or status 429) now mark the connection’s account using send-time identity and reroute the same turn. Uses resets_at or resets_in_seconds when present; otherwise falls back to cached window or bounded default. Mid-stream handling and no-replay gate are unchanged.
    • Raw WS upgrades (Bun and Node) now retain status/headers/body and finalize truncated bodies on close; malformed status lines fail cleanly; the error is emitted once.
    • Negative cases do not mark: 503 circuit-open, untyped statusless error, and websocket_connection_limit_reached (HTTP-fallback path).
  • Refactors

    • resolveMidStreamRateLimitResetAt accepts an explicit reset; onRateLimitReached now provides (window, accountId, resetAt).
    • Added parseRateLimitSignal and raw-ws-upgrade utilities to classify rate limits (including 429s with other error types) and structure rejected-upgrade errors.

Written for commit faaca3e. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/raw-ws-upgrade.ts
Comment thread packages/opencode/src/raw-ws-node.ts
Admission-time usage_limit_reached frames bypassed the response.failed handler, leaving the exhausted account unmarked so OpenCode's retry loop selected it again. Raw upgrade 429s also discarded the status and provider reset body.

Mark the connection's captured account before returning a ResponseStreamError. Its retryable APICallError contract is what OpenCode honors when reissuing the turn, while the explicit provider reset keeps the routing mark authoritative.
@iceteaSA
iceteaSA force-pushed the fix/ws-admission-rate-limit branch from 7643359 to faaca3e Compare July 19, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant