Skip to content

aibridge: successful Bedrock streaming responses logged as "interception failed" with error=EOF #25339

@dannykopping

Description

@dannykopping

Disclosure: issue identified and reproduced with the help of Claude Opus 4.7

Summary

When aibridge proxies a streaming /v1/messages request to AWS Bedrock and the upstream stream completes successfully, the interception is recorded as a failure with error=EOF. The client receives all events correctly — everything appears to work — but the server logs a misleading warning, the
aibridge_interceptions_total{status="failed"} metric is incremented, and the OTel span is marked Error.

Repro

Any successful /v1/messages SSE request routed through Bedrock. Example log line:

warn  coderd.aibridged.pool: interception failed  route=/v1/messages  provider=anthropic  streaming=true  error=EOF

Root cause

The Anthropic Go SDK's Bedrock event-stream decoder violates Go's iterator convention.

// bedrock/bedrock.go:

msg, err := e.Decoder.Decode(e.rc, nil)
if err != nil {
    e.err = err   // io.EOF on clean end gets captured here
    return false
}

Stream.Err() therefore returns io.EOF on clean stream completion, instead of nil (compare bufio.Scanner.Err(), the sibling SSE decoder, etc.).

In aibridge/intercept/messages/streaming.go, mapStreamError returns nil for this EOF (via IsUnrecoverableError), but the else if streamErr != nil capture (added in #24836) then records EOF as interceptionErr, which bridge.go logs as "interception failed" and counts as a failed interception.

The conflation also exists in eventstream.IsConnError, which includes io.EOF alongside EPIPE/ECONNRESET — semantically wrong: EOF is end-of-stream from upstream reads, while client disconnects manifest as EPIPE/ECONNRESET on writes.

Same pattern in aibridge/intercept/chatcompletions/streaming.go.

Impact

  • Misleading WARN-level logs on every successful Bedrock streaming completion.
  • aibridge_interceptions_total{status="failed"} over-counts; status="completed" under-counts.
  • OTel spans for successful interceptions are marked Error.
  • No user-visible effect on the response itself.

Suggested fix

if err != nil {
    if !errors.Is(err, io.EOF) { e.err = err }
    return false
}

Metadata

Metadata

Assignees

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions