Skip to content

[BUG] chunkTimeout does not work for AWS Bedrock and other non-SSE streaming protocols (EventStream) #26487

Closed

Description

@gkkkd8

Description

Issue Title

[BUG] chunkTimeout does not work for AWS Bedrock and other non-SSE streaming protocols (EventStream)


Issue Body

Summary

The chunkTimeout configuration option in provider.options completely fails to protect AWS Bedrock (and potentially Google Vertex AI) streaming requests. This is due to wrapSSE() in provider.ts checking for text/event-stream content-type, which silently skips all non-SSE protocols like AWS EventStream (application/vnd.amazon.eventstream).

Impact: Bedrock users have no chunk-level timeout protection against silent stream stalls, only the overall timeout (which may be too coarse-grained or disabled).


Reproduction

Environment

  • opencode: Installed via npm (~/.nvm/versions/node/v22.22.0/bin/opencode)
  • Provider: AWS Bedrock (@ai-sdk/amazon-bedrock)
  • Model: global.anthropic.claude-sonnet-4-6

Configuration

~/.config/opencode/opencode.json:

{
  "providers": {
    "my-bedrock": {
      "npm": "@ai-sdk/amazon-bedrock",
      "options": {
        "region": "us-west-2",
        "chunkTimeout": 10,
        "timeout": 60000
      }
    },
    "my-anthropic": {
      "npm": "@ai-sdk/anthropic",
      "options": {
        "apiKey": "sk-ant-...",
        "chunkTimeout": 10,
        "timeout": 60000
      }
    }
  }
}

Test A: AWS Bedrock (10ms chunkTimeout)

Command:

opencode run -m "my-bedrock/global.anthropic.claude-sonnet-4-6" \
  "请写一段300字的文章介绍科技发展"

Expected: Should timeout immediately (actual chunk interval ~50-100ms >> 10ms)

Actual Result:

✅ Request completed successfully
   Full 300-word article generated
   No timeout error

Measured chunk intervals (10 runs, 100-300 word outputs):

  • p50: 47ms
  • p95: 93ms
  • p99: 103ms
  • max: 440ms (spike)

All intervals far exceed the 10ms limit, yet no timeout was triggered.


Test B: Standard Anthropic API (10ms chunkTimeout, control group)

Command:

opencode run -m "my-anthropic/claude-sonnet-4-6" \
  "请写一段300字的文章介绍编程语言的发展"

Expected: Should timeout immediately

Actual Result:

❌ Error: SSE read timed out

Conclusion: chunkTimeout works correctly for standard SSE APIs, but completely fails for Bedrock.


Root Cause Analysis

Code Location

packages/opencode/src/provider/provider.ts, line 61-64:

function wrapSSE(res: Response, ms: number, ctl: AbortController) {
  if (typeof ms !== "number" || ms <= 0) return res
  if (!res.body) return res
  
  // ❌ THIS IS THE PROBLEM
  if (!res.headers.get("content-type")?.includes("text/event-stream")) 
    return res  // Bedrock bypasses here, no monitoring installed
  
  // ✅ Only standard SSE reaches this point
  const reader = res.body.getReader()
  const body = new ReadableStream<Uint8Array>({
    async pull(ctrl) {
      const part = await new Promise((resolve, reject) => {
        const id = setTimeout(() => {
          reject(new Error("SSE read timed out"))
        }, ms)
        // ... monitoring logic
      })
      // ...
    }
  })
  return new Response(body, ...)
}

Why Bedrock Bypasses

Bedrock Response Headers (verified):

content-type: application/vnd.amazon.eventstream

The Check:

"application/vnd.amazon.eventstream".includes("text/event-stream")
→ false

Result: wrapSSE returns the raw Response immediately, never installing the chunk timeout monitor.

Why EventStream ≠ SSE

AWS Bedrock uses AWS EventStream (a binary protocol, similar to protobuf), not standard SSE:

  • Standard SSE: text/event-stream, text format data: {...}\n\n
  • AWS EventStream: application/vnd.amazon.eventstream, binary framing

The AI SDK (@ai-sdk/amazon-bedrock) handles the binary decoding internally, so functionally everything works—except opencode's timeout layer never sees it.


Data Flow Comparison

Standard API (OpenAI, Anthropic direct)

opencode wrapSSE()
  → content-type: text/event-stream ✓
  → Wraps ReadableStream with setTimeout monitoring
  → AI SDK receives monitored stream
  → chunkTimeout works ✅

AWS Bedrock

opencode wrapSSE()
  → content-type: application/vnd.amazon.eventstream ✗
  → Returns raw Response, no monitoring
  → AI SDK decodes EventStream internally
  → Data arrives correctly, but no timeout protection ❌

Affected Scope

Provider Content-Type chunkTimeout Works?
Anthropic Direct text/event-stream ✅ Yes
OpenAI Direct text/event-stream ✅ Yes
AWS Bedrock application/vnd.amazon.eventstream ❌ No
Google Vertex AI Unknown (likely EventStream) ❓ Likely broken
Azure OpenAI text/event-stream ✅ Yes

Evidence for Vertex AI: Issue #13841 reports identical hanging symptoms with Vertex AI, suggesting it may also use a non-SSE protocol.


Why This Matters

Without chunkTimeout protection:

  1. Silent stream stalls (network glitch, model deadlock) go undetected for minutes
  2. Only timeout (total duration) can catch it, which is too coarse:
    • A 2-minute task could stall at 1:59 and not be detected for another minute
    • Long tasks (thinking models, large outputs) may disable timeout entirely
  3. Users rely on manual intervention (kill -9), as reported in Explore subagent hangs indefinitely with Anthropic Claude Opus 4.6 -- no timeout or recovery #13841, fix(opencode): reconnect on network disruptions (VPN switch, SSE timeout, connection reset) #19116

Measured Bedrock Performance

  • Total duration for 300-word output: ~5-6 seconds (normal)
  • Total duration for complex reasoning: ~60 seconds (normal)
  • Chunk intervals are very stable (p99=103ms), so a 30-second chunkTimeout would catch real stalls without false positives

Proposed Fix

Option A: Remove Content-Type Check (Simplest)

function wrapSSE(res: Response, ms: number, ctl: AbortController) {
  if (typeof ms !== "number" || ms <= 0) return res
  if (!res.body) return res
  
-  if (!res.headers.get("content-type")?.includes("text/event-stream")) 
-    return res
  
  const reader = res.body.getReader()
  // ... existing monitoring logic
}

Rationale: The function monitors raw ReadableStream chunks, which is protocol-agnostic. The AI SDK handles format decoding downstream, so monitoring the byte stream works for all protocols.

Risk: Minimal. Non-streaming responses (JSON) typically don't have res.body and are already filtered by the if (!res.body) return res check.


Option B: Expand Content-Type Match

- if (!res.headers.get("content-type")?.includes("text/event-stream")) 
+ const ct = res.headers.get("content-type") ?? ""
+ if (!ct.includes("text/event-stream") && !ct.includes("eventstream")) 
    return res

Rationale: Explicitly include EventStream variants.

Risk: May catch unintended protocols if any provider uses "eventstream" in a different context.


Option C: Allowlist by Provider

const NEEDS_TIMEOUT = ["@ai-sdk/anthropic", "@ai-sdk/openai", "@ai-sdk/amazon-bedrock", ...]
if (!NEEDS_TIMEOUT.includes(model.api.npm) && !ct.includes("text/event-stream"))
  return res

Risk: High maintenance burden, fragile as new providers are added.


Recommendation

Option A (remove check entirely) is the safest and simplest. The function already has sufficient guards (typeof ms, !res.body), and monitoring the raw byte stream is fundamentally protocol-agnostic.


Related Issues

Note: Even if #25575/#25586 were merged (propagating chunkTimeout to AI SDK's streamText), Bedrock would still be broken due to this wrapSSE filter.


Additional Context

This issue was discovered through controlled experiments comparing Bedrock vs. standard API behavior with identical 10ms chunkTimeout settings. Full analysis document available upon request.

System Details:

  • OS: macOS (Darwin 24.5.0)
  • Node: v22.22.0
  • opencode version: Latest from npm
  • Test date: 2026-05-09

Plugins

No response

OpenCode version

1.14.22

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. trollkarlen commented on Jul 7, 2026

    @trollkarlen

    Why no update on this issue ?
    chunkTimeout is more useful then timeout for streaming models.

    I vote for option B and with option for extra list of global setting for content-types that also included in the check:
    extraChunkedContentTypes: [],

  2. rock-solid-sites commented on Aug 3, 2026

    @rock-solid-sites

    [title: Controlled reproduction: non-SSE responses bypass chunkTimeout and hang indefinitely (zero errors, process stays RUNNING)]

    Reproduction confirms and sharpens the existing reports (#26487, #39516). Using opencode 1.18.11, I built a fake hanging provider server and tested three response modes against 'opencode run --model hangtest/hang-model' with chunkTimeout=3000:

    [table: text/event-stream+hang -> chunkTimeout fires ~3s, AI SDK retries with exp backoff, zero error entries, silent infinite retry loop, killed only by outer timeout EXIT=124; application/json+hang -> chunkTimeout NEVER fires, wrapSSE guard returns unwrapped, body hangs forever, 2 POSTs then silence, zero retries/errors, infinite hang; no headers -> headerTimeout fires 2s, ProviderHeaderTimeoutError, retries+visible errors, protected]

    The application/json row exactly matches the real-world production failure: last stream sent, then zero retry streams and zero error log entries for 10+ hours; process alive, agent looks RUNNING. If chunkTimeout had fired, log would show retry streams; it shows none.

    Root cause: content-type guard in wrapSSE means chunkTimeout is a no-op for non-SSE responses, and the overall request timeout (AbortSignal.timeout) does not abort the already-resolved body read. Any provider returning application/json or non-SSE content-type can hang indefinitely.

    Suggested fix (matches #39516/#35743): remove/relax the text/event-stream guard so chunkTimeout applies to all response bodies, or add a general body-read deadline.

    Impact note: same symptom class as #40205 (opencode-go streaming freeze v1.18.11).

    Environment: opencode 1.18.11, Linux, CLI.

  3. pkumar-cloud commented on Aug 11, 2026

    @pkumar-cloud

    Corroborating this from a different environment: hit the exact same symptom independently — an interactive OpenCode session (model us.anthropic.claude-sonnet-5 via a custom provider backed by @ai-sdk/amazon-bedrock, routed through a corporate LLM gateway that fronts Bedrock) went completely unresponsive with zero error output, for the same reason described here: the upstream gateway silently stalled the EventStream response mid-turn, and since wrapSSE's text/event-stream content-type check never matches application/vnd.amazon.eventstream, no chunk-timeout monitoring was ever installed regardless of chunkTimeout being configured. Had two separate sessions hang this way on consecutive days with no recovery short of killing the process.

    For contrast: I separately diagnosed and fixed the equivalent problem for Claude Code (non-OpenCode) hitting the same shared gateway — Claude Code's own idle-stream watchdog (API_FORCE_IDLE_TIMEOUT) is off by default on the Bedrock path too, but turning it on resolves it cleanly there. OpenCode has no equivalent fallback for the Bedrock/EventStream path today, so this is a genuine no-mitigation gap, not just a rough edge — chunkTimeout silently does nothing here, which is worse than not having the option at all since it looks configured.

    Noting that the fix PR (#35743) that closes this issue via Option A/B in the writeup above was closed by automated-pr-cleanup (stale-bot), not on technical merits — worth reopening/rebasing given this is still live and causing indefinite hangs in production use. Happy to test a rebased version against our setup if useful.

  4. rekram1-node commented on Oct 2, 2026

    @rekram1-node
    Collaborator

    Fixed on v2 in #49229, available in v2.0.22.

    V2 doesn't rely on aisdk for bedrock, so the timeouts work as expected:

    https://opencode.ai/v2/docs

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions