Skip to content

sub2api sometimes returns a stream without a terminal signal, causing opencodex to wait indefinitely #735

Closed

Description

@jinbaizhe

Client or integration

Codex App

Area

Proxy and routing

Summary

I’m using opencodex as a local proxy, and I found that some models routed through sub2api can get stuck in a “waiting” state.

Observed error:
stream disconnected before completion: Provider stream error: upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation
What I observed:
The request often returns HTTP 200.
firstOutputMs is normal, so the upstream is not completely silent.
But the final terminal signal is missing, so opencodex keeps waiting for completion.
In practice, this shows up as a 300000ms timeout or a permanently spinning / waiting Desktop session.
Additional note:
When I configure sub2api directly under ~/.codex/.config.toml, the same model can work normally.
The issue appears more reliably when the request goes through opencodex.
My current suspicion is:
sub2api does not consistently emit a terminal SSE signal such as [DONE] or finish_reason
or opencodex is not handling this stream-ending behavior robustly enough when that signal is missing
Environment:
opencodex: 2.7.43
Codex Desktop: 0.146.0-alpha.3.1
Affected models:office/gpt-5.6-terra(using the model provided by sub2api)

Reproduction

Reproduction steps:
Use Codex Desktop through opencodex.
Select office/gpt-5.6-terra(Using the model provided by sub2api)
Send a normal prompt.
The request starts streaming output, but it never finishes cleanly. The UI keeps waiting until timeout or manual interruption.
Observed error:
stream disconnected before completion: Provider stream error: upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation

Version

2.7.43

Operating system

macos 15.7

Provider and model

No response

Logs or error output

stream disconnected before completion: Provider stream error: upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation

Screenshots and supporting files

Image

Redacted configuration

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. github-actions commented on Jul 30, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: unknown.

  2. Wibias commented on Jul 30, 2026

    @Wibias
    Contributor

    [shipping-github] Research review

    Claim: sub2api / openai-chat upstreams can end SSE without [DONE]/finish_reason, leaving Codex waiting until timeout.
    Checked against: dev@be177ea5

    Field Finding
    Still an issue on latest development? yes (fail-closed on missing terminal still active)
    Exact issue openai-chat adapter errors when EOF arrives with neither finish_reason nor usage; bridge surfaces truncation
    Fixed on development? no — current behavior is intentional fail-closed, not silent completion
    Open PR covering this? none
    Duplicate of? related open #658 (AgentRouter Anthropic missing terminal frames); same class, different adapter
    Security relevance none — n/a
    Priority middle — breaks affected routed models in Desktop; direct-to-sub2api reportedly works
    Verdict Needs fix on development (provider quirk tolerance and/or clearer incomplete completion)

    Evidence: src/adapters/openai-chat.ts ~804–814 yields upstream stream ended without a terminal signal.... Message matches reporter.
    Gaps: No live sub2api repro in this session; unclear whether upstream emits usable content before EOF that could safely synthesize done.

  3. Wibias commented on Jul 30, 2026

    @Wibias
    Contributor

    [shipping-github] Opened PR #773 to address this.

  4. added a commit that references this issue on Jul 30, 2026
  5. lidge-jun commented on Jul 31, 2026

    @lidge-jun
    Owner

    Half of this is fixed on codex/260731-pr-merge-round (38f711beb); the other half is a policy call I am leaving to a maintainer.

    Landed — a truncated tool call no longer reports success. At EOF the adapter flushed buffered tool calls and only then judged whether the stream had terminated cleanly. flushToolCalls() emits tool_call_end, so a socket that died mid-argument handed the client a half-assembled call marked complete. Nothing downstream could tell it apart from a real one, which is worse than an error. The integrity check now runs before the flush, and a trailing usage frame no longer launders it either.

    This adapter can make that decision because it buffers tool calls. The Anthropic adapter forwards fragments live and genuinely cannot — that attempt was tried twice and reverted this round (devlog 260731_pr_merge_round/021).

    Not landed — completing text-only streams that never terminate. That is the part that would close your sub2api case, and it is a policy change rather than a bug fix: it inverts two existing assertions in tests/openai-chat-eof.test.ts that currently pin "content without a terminator is truncation".

    That decision needs context I do not have. #773 implemented it, was merged, and was reverted by #792 within a day — and #792's body says only "Reverts #773", with no discussion anywhere. #793 proposes re-landing the same behavior without addressing why it was pulled. Re-applying a change whose revert reason is unrecorded is how the same problem returns twice, so I stopped rather than guess.

    @lidge-jun — if you remember why #773 was reverted, that unblocks #793 immediately. If the revert was precautionary rather than a real regression, the fix is small and the tests it inverts can be updated deliberately instead of by accident.

    Staying open for that half.

  6. Wibias commented on Jul 31, 2026

    @Wibias
    Contributor

    Thanks @jinbaizhe — this is fixed by PR #793 by completing openai-chat streams that end after answer text without a terminal frame, while still failing closed when EOF leaves a tool call mid-assembly.

  7. Wibias commented on Jul 31, 2026

    @Wibias
    Contributor

    @lidge-jun On the revert reason for #773 / #792:

    #773 was merged without review by accident. #792 pulled it back for that reason — not because of a known runtime regression. There was no recorded product objection to the finish-less text EOF behavior itself.

    #793 re-lands the same policy deliberately: content without a terminator completes when answer text was emitted; mid-assembly tool-call EOF (and reasoning-/usage-only truncations) still fail closed. That second half is now on dev via #793 (679eda00).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions