Skip to content

Wrapped stream error chunks bypass retry and can leave parent sessions waiting forever #21979

Closed as not planned

Description

@tossp

Description

When a provider using the Responses streaming path returns a wrapped error chunk such as:

{"error":{"code":"","message":"","type":"server_error"},"request_id":""}

OpenCode currently tries to validate it as a normal response.* stream event and fails with:

  • Type validation failed
  • invalid_union

This appears to happen before any terminal event like response.completed / response.incomplete is seen.

That makes the failure behave like a stream protocol failure before terminal event, but it is currently surfaced as a schema/type validation error instead of a retryable API/stream failure.

This is especially problematic for subagents/background tasks: the child stream can die without entering a proper terminal failed/completed state, while the parent keeps waiting for the child response indefinitely.

Based on local investigation:

  • the Responses path only treats response.completed / response.incomplete as terminal
  • wrapped error chunks are not normalized before schema validation
  • existing retry/backoff is already wired for APICallError -> MessageV2.APIError -> SessionRetry.retryable()
  • plugin hooks can modify request params/headers, but cannot intercept/normalize response stream chunks before validation

So the current behavior seems to be:

  1. provider sends wrapped error chunk in stream
  2. chunk is validated as normal response.* event
  3. union validation fails (invalid_union)
  4. failure does not enter the normal retryable API error path
  5. parent/subagent waiting logic may never get a proper terminal failure signal

Plugins

oh-my-opencode

OpenCode version

Observed on recent OpenCode builds using the Responses streaming path; local repo analysis was against anomalyco/opencode dev and a local installation that surfaced the bundled stack in the TUI worker.

Steps to reproduce

  1. Use a provider/model path that goes through the Responses streaming implementation.
  2. Have the upstream stream return a wrapped error chunk like:
{"error":{"code":"","message":"","type":"server_error"},"request_id":""}
  1. Observe that OpenCode reports a Type validation failed / invalid_union error instead of handling it like a retryable stream/API failure.
  2. In subagent/background-task scenarios, observe that the parent can continue waiting because the child did not complete with a normal terminal event.

Screenshot and/or share link

Example error payload observed locally:

Type validation failed: Value: {"error":{"code":"","message":"","type":"server_error"},"request_id":""}
Error message: invalid_union ...

Operating System

Linux

Terminal

N/A


Expected behavior

Wrapped stream error chunks / premature stream failures before response.completed or response.incomplete should be treated as something like:

  • stream terminated before terminal event
  • or stream ended without completed response

and then mapped into the existing retry/backoff flow (for example via APICallError with the correct retryability), instead of surfacing as raw schema validation failure.

That would make this behave more like retryable HTTP/provider failures and also ensure parent sessions/subagents receive a proper terminal failure signal.

Suggested direction

For the Responses streaming path:

  • skip empty/meta-only frames
  • detect wrapped error chunks such as { "error": { ... } }
  • normalize them before schema validation, or convert them directly into a retryable APICallError
  • if the stream ends without response.completed / response.incomplete, treat that as a stream termination failure rather than a generic validation error

This would avoid the invalid_union surface error and should also prevent parent agents from waiting forever on failed child streams.

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 11, 2026
  2. github-actions commented on Apr 11, 2026

    @github-actions
    Contributor

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

    • Some transient stream/rate-limit errors bypass retry and terminate sessions #21893: Covers the same class of problem — stream validation errors (including wrapped rate_limit_error / stream_read_error chunks) bypassing the retry path and terminating sessions. The error shapes, affected code paths (openai-responses-language-model.ts, retry.ts), and root cause described there overlap significantly with this report.

    If this issue covers a distinct aspect (specifically the Responses streaming path + parent/subagent indefinite wait), please clarify the difference so maintainers can track it separately.

  3. tossp commented on Apr 11, 2026

    @tossp
    Author

    Thanks — I agree there is substantial overlap with #21893 on the retry-bypass side.

    The distinct part I wanted to capture here is the parent/subagent waiting behavior in the Responses streaming path.

    When this happens inside a subagent/background task, the child can fail before any terminal stream outcome is observed, and the parent may continue waiting indefinitely for the child result.

    So from my perspective:

    I’m also adding the concrete error shape here because this one did not just stay internal — it surfaced all the way to the TUI as a raw validation error:

    Type validation failed: Value: {"error":{"code":"","message":"","type":"server_error"},"request_id":""}.
    Error message: [
      {
        "code": "invalid_union",
        "path": [],
        "message": "Invalid input"
      }
    ]
    

    And the important detail is that this is not a normal response.* terminal event — it is a wrapped error chunk arriving on the stream before response.completed / response.incomplete.

    So the user-visible behavior is:

    1. wrapped stream error chunk arrives
    2. it is validated as a normal Responses event and fails with invalid_union
    3. the raw validation error can reach the TUI
    4. in subagent/background-task cases, the parent may keep waiting because the child did not produce a normal terminal outcome

    If maintainers prefer, I’m happy to treat this as a sub-case of #21893 and close this one — but I wanted to explicitly call out the subagent/background-task hang and the fact that the raw validation failure is surfacing at the UI layer.

  4. tossp commented on Apr 11, 2026

    @tossp
    Author

    I have a more precise update after building a processor-level regression around the exact failing payload on openai/gpt-5.4:

    {"error":{"code":"","message":"","type":"server_error"},"request_id":""}

    What the regression confirms:

    • the final session error currently becomes an UnknownError
    • its message contains the raw SDK validation failure text:
      • Type validation failed
      • invalid_union
      • server_error
    • so the failure is escaping the SDK boundary and polluting the TUI/session error path instead of being translated into a controlled OpenCode error

    More precise framing:

    • the immediate throw appears to happen in the upstream SDK Responses validation path
    • but OpenCode still needs to contain/remap that SDK error into its own session/provider error semantics
    • otherwise:
      • the TUI shows raw validation internals
      • the main agent can stop in the wrong state
      • subagent/parent flows do not get a clean, controlled failure path

    One important nuance from testing:

    • this is not just a local Responses schema issue inside OpenCode
    • the main OpenCode-side problem is that this SDK validation error is currently surfacing as UnknownError instead of being translated into a proper controlled error type

    So the issue is broader than "a wrapped error chunk missed a schema branch" — the key problem is that a wrapped upstream server error can turn into a raw SDK validation error, and OpenCode does not currently translate that into safe session-level error behavior.

  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. ririnto commented on May 22, 2026

    @ririnto

    I have a more concrete repro shape for the parent/subagent waiting part of this issue, from a sanitized local log on OpenCode 1.15.7.

    This happened with a subagent child session on an OpenAI model. The upstream stream emitted a wrapped overload error:

    service=llm providerID=openai modelID=<openai_model> session.id=<child_session> agent=<subagent_agent> mode=subagent error={"error":{"type":"error","sequence_number":2,"error":{"type":"service_unavailable_error","code":"server_is_overloaded","message":"Our servers are currently overloaded. Please try again later."}}} stream error
    

    The important sequence was:

    T+00:00 child session created (version=1.15.7)
    T+00:53 service=llm ... code=server_is_overloaded ... stream error
    T+00:53 session.prompt cancel
    T+30:00 session.prompt cancel
    T+30:00 fallback child session created with the same parent/title on a fallback OpenAI model
    

    So in this observed case, the child did not visibly remain in retry for 30 minutes. Instead, the child stream failed and the child session moved through the cancel path, but the parent/sync task did not receive a clean terminal failure/completion signal. It waited until the 30-minute inactivity window and then created a fallback child session.

    That makes the user-visible behavior slightly different from a pure infinite retry loop:

    1. wrapped provider stream error arrives
    2. child session is cancelled/idled
    3. parent/subagent waiter is not resolved with a terminal failure
    4. parent appears hung until the sync inactivity window expires
    5. fallback child is created later

    No local paths, project paths, API keys, request IDs, or user content are included here. Session IDs and model names above are intentionally masked.

    I am going to try a small PR scoped to the two seams this seems to expose: wrapped provider stream errors becoming controlled retryable session errors, and child assistant terminal errors being propagated as task failures instead of empty successful task results.

  7. github-actions commented on Jul 22, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions