Skip to content

[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 #46564

Closed as not planned

Description

@gripeinst

[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

  1. 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).
  2. The provider returned 400 "invalid request" → assistant message recorded with "error": {"type":"provider.invalid-request","message":"invalid request","status":400}, finish: "error".
  3. No compaction recovery was attempted (no session.compaction.* events in the server log for that run); the session repeatedly failed with ERROR "Failed to drain Session" cause="AI.Error: invalid request" in a loop.
  4. 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.
  5. 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)

  1. 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).
  2. 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.
  3. 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

  1. github-actions commented on Sep 1, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. Kouskous56 commented on Oct 2, 2026

    @Kouskous56

    Reproduced on the first-party opencode provider, which makes this deterministic rather than endpoint-specific. Model muse-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:

    payload chars Token.estimate (chars/4) result
    1,200,000 300,000 200 OK
    1,600,000 400,000 400 invalid_request_error
    2,000,000 500,000 400 invalid_request_error

    Error 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\"}}"}}}

    code and param are both null, so there is nothing machine-readable to match on. I grepped the shipped opencode-cli 2.0.21 binary for the classifier patterns: the only occurrence of the string invalid parameters is a JS engine internal, not a pattern.

    Recovery works whenever the estimate crosses the ceiling — the same 4.6M-char prompt with auto: true compacted, 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:

    No user-side retry is possible: each retry re-sends the identical oversized body. Only /compact or a model switch recovers the session.

  3. ImBIOS commented on Oct 14, 2026

    @ImBIOS
    Contributor

    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: false recovery 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.

  4. rekram1-node commented on Oct 16, 2026

    @rekram1-node
    Collaborator

    Thanks for the report, and thanks @Kouskous56 for reproducing it.

    • auto: false turns off all automatic compaction, including recovery after an overflow. If you want OpenCode to compact, leave it on.
    • The bare 400 doesn't say anything about context, and we don't want to treat every 400 as an overflow.

    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.

  5. ImBIOS commented on Oct 18, 2026

    @ImBIOS
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions