Repository navigation
Description
[v2] One-shot provider-overflow recovery does not engage when the provider returns an opaque 400 (e.g. bare "invalid request") — session dead-ends with no recovery
Environment
- opencode2
v0.0.0-beta-18743(Windows) - Custom openai-compatible provider (Bitdeer
api-inference.bitdeer.ai, model context limit 262,144 / max output 32,768) compaction: { "auto": false }(required when using a third-party context-management plugin)
Background
Per the V2 compaction docs: V2 "recognizes provider errors classified as context overflow" and performs one-shot compaction + retry "even when auto is false". Our provider rejects oversized requests with a bare 400 {"message":"invalid request"} — no "context length" / "too long" wording — so the error is not classified, and the only remaining safety net never engages.
With auto: false, preflight compaction is off by definition, so the overflow-recovery path is the only protection left — and opaque 400s bypass it entirely.
What happened
- A long session's transcript grew to ~256K tokens (independently measured by a loaded plugin:
tokensBefore: 256320, 408 messages, ~98% of the 262,144 window; plus system prompt + tool schemas → over the limit). - The provider returned
400 "invalid request"→ assistant message recorded with"error": {"type":"provider.invalid-request","message":"invalid request","status":400},finish: "error". - No compaction recovery was attempted (no
session.compaction.*events in the server log for that run); the session repeatedly failed withERROR "Failed to drain Session" cause="AI.Error: invalid request"in a loop. - The user reverted to several earlier messages to recover; each post-compaction revert point still left a > 250K-token payload (checkpoint summary + tail), so the same 400 repeated. Only reverting to a point before the last compaction checkpoint (dropping it, ~170K-token prefix) recovered the session.
- Session state:
outcome: "failed".
Why this is a V2 gap
- Preflight check: disabled by configuration (legitimate use case — third-party context plugins).
- Overflow recovery: requires the provider error to be classified as context overflow. A bare 400 with an uninformative message cannot be classified, so recovery is skipped.
- Result: a session can cross the window and become unrecoverable by any user action short of reverting far enough to fall under the limit blind (the TUI gives no context-size feedback at the point of failure).
Suggestions (any of these would help)
- Heuristic one-shot recovery: when a 4xx arrives and the estimated request size (V2 already computes this — JSON-serialized request, 4 chars/token) exceeds the model's context/input limit, treat the step as an overflow candidate: compact + retry once (same guard as the classified path).
- Per-provider overflow-error patterns: a config option (e.g.
providers.<id>.overflowErrorPatterns: ["invalid request"], matched against the provider error message) so users can classify opaque errors explicitly. - Minimum — surface the size: when a model step fails with a 4xx on a request whose estimate exceeds the window, include the numbers in the TUI error, e.g.
request ≈ 256,320 tokens > 262,144 window — compact the session or revert to an earlier point. This alone turns an opaque dead-end into an actionable one.
Repro recipe
Any custom openai-compatible endpoint that answers oversized prompts with a bare 400 + a session that grows past the model window while compaction.auto: false (or before the preflight threshold on a slow-growing session). Observed with Bitdeer inference + Qwen3.8-27B (262K window).
Related: opencode-acp tracks the same failure class on the V1 side (ranxianglei/opencode-acp#346, #347 — silent overflow death on custom providers).
Activity
github-actions commentedon Sep 1, 2026
This issue might be a duplicate of existing issues. Please check:
ContextWindowExceededErrorwrapper; this report covers opaque bare 400s from custom OpenAI-compatible endpoints. The underlying gap is identical: the overflow matcher inprovider-error.tsdoesn't recognize the error, so the one-shot recovery path is skipped and the session dead-ends.Kouskous56 commentedon Oct 2, 2026
Reproduced on the first-party
opencodeprovider, which makes this deterministic rather than endpoint-specific. Modelmuse-spark-1.3-contributor-free,limit.context = 1,048,576.Measured through the running server (2.0.21) with
compaction: { "auto": false }, sending a single user message of random hex so payload size is the only variable:Token.estimate(chars/4)invalid_request_errorinvalid_request_errorError stored on the assistant message:
{"finish":"error","error":{"type":"provider.invalid-request","status":400, "message":"The request contains invalid parameters. Check the request body for any errors or inconsistencies.", "response":{"body":"{\"model\":\"muse-spark-1.3-contributor-free\",\"error\":{\"code\":null,\"message\":\"...\",\"param\":null,\"type\":\"invalid_request_error\"}}"}}}codeandparamare bothnull, so there is nothing machine-readable to match on. I grepped the shippedopencode-cli2.0.21 binary for the classifier patterns: the only occurrence of the stringinvalid parametersis a JS engine internal, not a pattern.Recovery works whenever the estimate crosses the ceiling — the same 4.6M-char prompt with
auto: truecompacted, rebuilt to 8,198 input tokens, and returned 200. So the missing piece here really is classification of the opaque 400, and suggestion 1 in this thread would have covered this case.Two caveats for that suggestion:
auto: falseand the overflow confined to a single message,compactAfterOverflowreturnsfalseanyway (selected.head.length === 0), so classification alone would not have recovered this specific session.No user-side retry is possible: each retry re-sends the identical oversized body. Only
/compactor a model switch recovers the session.ImBIOS commentedon Oct 14, 2026
Open PR that should fix the default-config variant of this: #55286. It treats an unclassified 4xx against a request at or above 4 MiB as a payload rejection (one text-only compaction rebuild). One caveat from the implementation: with
compaction.auto: falserecovery still declines per existing opt-out semantics, so your exact env (auto:false + third-party plugin) would additionally need a policy decision on failure-driven recovery bypassing the opt-out.rekram1-node commentedon Oct 16, 2026
Thanks for the report, and thanks @Kouskous56 for reproducing it.
auto: falseturns off all automatic compaction, including recovery after an overflow. If you want OpenCode to compact, leave it on.For Muse, the generic error comes from Meta itself. I'm asking them to return a proper context-length error, which our existing recovery already handles.
ImBIOS commentedon Oct 18, 2026
Update: the previous fix PR #55286 was auto-closed on a template technicality (not on merit). Re-opened as #56767 with the template filled, rebased on current dev, full suites green. Same approach: unclassified 4xx + oversized body triggers one text-only compaction rebuild.