Skip to content

📡 fix: Explain Model Streams That Die Mid-Response - #16415

Merged
danny-avila merged 2 commits into
devfrom
danny-avila/provider-stream-failures
Sep 27, 2026
Merged

danny-avila merged 2 commits into
devfrom
danny-avila/provider-stream-failures

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

When a model provider accepts an agent request (HTTP 200) and the response then dies in transit, the turn ends with "The model provider could not complete this request." followed by the raw transport word terminated. Two very different failures produce that same text, and neither tells the reader what happened or what to do.

The first is a provider hanging up mid-stream. The DeepSeek report in #16372 shows api.deepseek.com closing the connection after about 4 minutes of reasoning ("other side closed"). The second is a provider that stops sending anything while keeping the connection open. On a production deployment, an OpenAI-compatible gateway's /v1/responses returned 200 and then sent nothing until LibreChat's own 15-minute model response body timeout (modelResponseBodyTimeoutMs, #16201) cut it off. That happened three times in 30 hours, each costing a user a 15-minute wait.

This change classifies both failures from the undici error in the transport error's cause chain and gives each its own typed payload: model_stream_closed and model_stream_stalled. The client renders each as a localized sentence that says what happened, that anything already received was kept, and what to try next. The terminal log line now carries errorType: stream_closed or stream_stalled in place of _OTHER, so log queries can tell these failures apart from other unclassified ones. A user pressing Stop still goes down the cancellation path unchanged, because an abort never carries these codes.

Automatically retrying a call that died mid-stream needs a change in @librechat/agents: the SDK owns the model call and has no hook that can reset the partially streamed step. That is left to a follow-up.

Related to #16372

How it works

Fetch reports both failures as the same TypeError: terminated. Only the undici error in its cause separates them:

TypeError: terminated
  cause: SocketError        code UND_ERR_SOCKET        -> model_stream_closed
  cause: BodyTimeoutError   code UND_ERR_BODY_TIMEOUT  -> model_stream_stalled

getModelStreamFailure walks a bounded cause chain (the same depth and hostile-accessor handling as isStepLimitError). A stall wins over a close, because the timeout is what ended the request. The terminal observer only consults it after the existing LangChain classification, and only for errors the model callback tracked, so failures outside the model call keep their current handling.

Type of change

  • Bug fix

Testing

The classifier is tested against real transport failures, not hand-built error shapes. A local HTTP server sends one SSE event and then either destroys the socket, goes silent past a 150 ms undici body timeout, or is aborted by the caller. The resulting errors are checked when read raw, and when streamed through ChatOpenAI from @librechat/agents for both Chat Completions and the Responses API (the gateway's path). The abort case confirms that Stop stays unclassified.

Tested environments/configuration:

  • Node 24.16.0, undici 7, @librechat/agents 3.9.6

Automated tests:

  • packages/api: npx jest src/agents/errors.spec.ts src/agents/failures (104 passed)
  • client: npx jest src/components/Messages/Content/__tests__/Error.spec.tsx (140 passed)
  • api: npx jest server/controllers/agents/client.test.js -t "upstream|Upstream|provider" (24 passed)
  • npx tsc --noEmit in packages/api and client, and eslint on the changed files

Screenshots / recordings

Not captured. The two new types render through the existing single-sentence error copy path, the same one as model_rate_limit, with no layout or styling change. Both cases are added to the seeded error gallery (config/create-error-convo.js) for a visual check.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T19:17:10.775332Z c5cbcc8 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a706dec4a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/agents/errors.ts Outdated
Comment thread packages/api/src/agents/errors.ts Outdated
@lia-by-librechat

Copy link
Copy Markdown
Contributor

Exact PR head: c5cbcc8. Both inline Codex findings are addressed, and the wider audit added pre-response, HTTP-status, real SDK callback, partial-output, privacy, and older-client coverage. A maintainer can request review of this exact head; the earlier review of a706dec does not cover it.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review the latest head

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: c5cbcc8091

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 8ec632f into dev Sep 27, 2026
44 checks passed
@danny-avila
danny-avila deleted the danny-avila/provider-stream-failures branch September 27, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants