Repository navigation
Description
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")
→ falseResult: 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 formatdata: {...}\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:
- Silent stream stalls (network glitch, model deadlock) go undetected for minutes
- 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
timeoutentirely
- 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 resRationale: 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 resRisk: 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
- Explore subagent hangs indefinitely with Anthropic Claude Opus 4.6 -- no timeout or recovery #13841: Explore subagent hangs indefinitely (Vertex AI, no timeout)
- fix(opencode): reconnect on network disruptions (VPN switch, SSE timeout, connection reset) #19116: Network reconnection fixes (mentions chunk timeout defaults to 30s)
- fix: propagate chunkTimeout to streamText timeout.chunkMs for silent stream dropout detection #25575, fix: propagate chunkTimeout to streamText and add max_retries config #25586: PRs to propagate chunkTimeout to AI SDK (closed due to formatting)
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
trollkarlen commentedon Jul 7, 2026
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: [],rock-solid-sites commentedon Aug 3, 2026
[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.
pkumar-cloud commentedon Aug 11, 2026
Corroborating this from a different environment: hit the exact same symptom independently — an interactive OpenCode session (model
us.anthropic.claude-sonnet-5via 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 sincewrapSSE'stext/event-streamcontent-type check never matchesapplication/vnd.amazon.eventstream, no chunk-timeout monitoring was ever installed regardless ofchunkTimeoutbeing 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 —chunkTimeoutsilently 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.rekram1-node commentedon Oct 2, 2026
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