You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
sub2api sometimes returns a stream without a terminal signal, causing opencodex to wait indefinitely #735
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
Redacted configuration
Checks
I searched existing issues and documentation.
I removed secrets, tokens, account details, request credentials, and personal data.
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.
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.
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.
#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).
Activity
github-actions commentedon Jul 30, 2026
Automated translation bookkeeping — detected language: unknown.
Wibias commentedon Jul 30, 2026
[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
openai-chatadapter errors when EOF arrives with neitherfinish_reasonnor usage; bridge surfaces truncationEvidence:
src/adapters/openai-chat.ts~804–814 yieldsupstream 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.Wibias commentedon Jul 30, 2026
[shipping-github] Opened PR #773 to address this.
fix: address issue #735
fix(openai-chat): fail a truncated tool call before it is flushed as …
lidge-jun commentedon Jul 31, 2026
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()emitstool_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.tsthat 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.
Wibias commentedon Jul 31, 2026
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.
Wibias commentedon Jul 31, 2026
@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
devvia #793 (679eda00).fix: address issue lidge-jun#735
fix(openai-chat): fail closed on EOF with pending tool calls
fix(openai-chat): fail a truncated tool call before it is flushed as …