Skip to content

fix(streaming): mark the research span as ERROR when the stream fails - #930

Merged
miurla merged 1 commit into
mainfrom
fix/langfuse-stream-error-level-20260804
Aug 3, 2026
Merged

fix(streaming): mark the research span as ERROR when the stream fails#930
miurla merged 1 commit into
mainfrom
fix/langfuse-stream-error-level-20260804

Conversation

@miurla

@miurla miurla commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Problem

When the chat stream fails, the failure never reaches the tracing surface. The root research observation ends with level: DEFAULT and statusMessage: null even though the turn produced no answer at all.

This matters because tracing is the only surface that covers guest and ephemeral executions, and it is what gets scanned for error-level observations. A recent provider outage produced a large number of consecutive turns where every observation on the research path was recorded at DEFAULT with zero tokens and no output. The failure was only visible because title-generation runs through generateText, which throws and therefore does get recorded as an error. Had the chat path been the only caller, the outage would have produced no error-level observation whatsoever.

Root cause

The provider error arrives as an error chunk inside the response stream rather than as a thrown exception, so the surrounding try/catch never runs and the request completes with HTTP 200. The onError callback passed to toUIMessageStreamResponse does fire, and it logs the error and serializes it for the client, but nothing marks the span. endTracing() then calls rootSpan.end() on an unmodified span.

Fix

Capture the error in both entry points (create-chat-stream-response.ts and create-ephemeral-chat-stream-response.ts), from the stream onError callback and from the surrounding catch, and mark the root span with level: 'ERROR' plus a status message before it is ended.

The status message comes from a new describeStreamError helper that uses only the code and error fields of the existing public error payload. That payload is already sent to every client, so the trace cannot pick up user content, a provider response body, or credentials. The result is capped at 300 characters, which matters because some public payloads are passed through verbatim.

This is additive only. Response status codes, the serialized public error payload, and the existing logging are unchanged.

Client aborts are not affected: aborts travel as a distinct abort chunk and set isAborted on onFinish, so they never reach onError and will not be marked as errors.

Verification

  • New unit tests cover a provider billing failure, an unclassified Error, a non-Error value, and the length cap, asserting the exact resulting message and that raw input text never appears in it.
  • bun run test 295 passed, bun typecheck, bun lint, bun format:check and bun run build all pass.
  • The billing case is asserted against the exact error shape observed in production, which resolves to provider_billing: The AI service is currently unavailable.

Provider failures on the chat stream are logged and serialized to the
client, but the Langfuse root observation still ends with level DEFAULT
and no status message. Langfuse is the only surface that covers guest
executions, so a provider outage leaves no error-level observation on
the research path at all.

Capture the error from the stream onError callback and the surrounding
catch, then mark the root span with level ERROR and a status message
before it is ended.

The status message is built only from the public error payload's code
and message, which is already sent to every client, so no user content
or provider response body reaches the trace.
@miurla miurla added the automated Created by the automated daily pipeline label Aug 3, 2026
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
morphic Ready Ready Preview Aug 3, 2026 10:53pm

Request Review

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

Labels

automated Created by the automated daily pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant