{"record":{"id":"ca9ac6663e155bc5","repo":"headroomlabs-ai/headroom","slug":"bedrock-upstream-exception","errorCode":"bedrock_upstream_exception","errorMessage":"Bedrock stream emitted exception: {payload_preview}","messagePattern":"Bedrock stream emitted exception: (.+?)","errorType":"exception","errorClass":"TranslateError::UpstreamException","httpStatus":null,"severity":"error","filePath":"crates/headroom-proxy/src/bedrock/eventstream_to_sse.rs","lineNumber":111,"sourceCode":"    /// passthrough mode is handled by the streaming forwarder, which\n    /// never bothers parsing).\n    Emit(Bytes),\n    /// Skip — the message has no client-facing translation. Used for\n    /// `:event-type` values that AWS emits for protocol-internal\n    /// signalling (not yet observed for Bedrock Anthropic responses,\n    /// but we surface a structured outcome rather than guess).\n    Skip { event_type: String },\n}\n\n/// Errors during translation. Per project rules these are loud — the\n/// handler 5xx's the client when an unknown `:message-type` arrives,\n/// rather than silently swallowing.\n#[derive(Debug, thiserror::Error)]\npub enum TranslateError {\n    /// `:message-type == exception`. AWS reserved value indicating the\n    /// service raised a synchronous error mid-stream. Surfaced as a\n    /// structured error so the handler can map it to a 5xx + log.\n    #[error(\"Bedrock stream emitted exception: {payload_preview}\")]\n    UpstreamException { payload_preview: String },\n    /// The translator's input message is missing a required header\n    /// (`:event-type`). Wire-format violation — AWS would never emit.\n    #[error(\"Bedrock message missing required `:event-type` header\")]\n    MissingEventType,\n}\n\n/// Translate one EventStream message under the chosen output mode.\n///\n/// Side-effect-free: emits a `tracing::info!` per `chunk` translation\n/// and `tracing::warn!` for unknown event types. The hot path is\n/// allocation-bounded — one `BytesMut` of `payload.len() + 8`.\npub fn translate_message(\n    message: &EventStreamMessage,\n    mode: OutputMode,\n) -> Result<TranslateOutcome, TranslateError> {\n    // Always check for `:message-type == exception` first — that's a\n    // structural error regardless of mode. AWS reserves this value to","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/crates/headroom-proxy/src/bedrock/eventstream_to_sse.rs#L93-L129","documentation":"Raised by the eventstream-to-SSE translator when an inbound Bedrock EventStream message carries :message-type == exception — AWS's reserved value indicating the service raised a synchronous error mid-stream. The payload preview is embedded in the message. Per project rules this is surfaced loudly (mapped to a 5xx plus a log) rather than swallowed, because the stream is dead once an exception frame arrives.","triggerScenarios":"Calling Bedrock invoke-model-with-response-stream (or converse-stream) where the model/access path fails after the 200 headers were already sent: throttling mid-stream, model overloaded, content policy trip, or an internal service error emitted as an exception frame.","commonSituations":"Exceeding Bedrock quotas mid-generation; using a model ID not enabled in the region; IAM/invoked-but-revoked credentials that pass initial auth then fail on the stream; a very long generation crossing an idle/stream timeout.","solutions":["Read the payload_preview embedded in the error message — it contains the Bedrock exception JSON (e.g. throttlingException, modelStreamErrorException, accessDeniedException) which names the real cause.","For throttling: enable retry with backoff on the upstream call and reduce concurrent streaming requests against the account's TPS quota.","For model/access errors: verify the model ID is available and invoked in that region and the IAM identity has bedrock:InvokeModelWithResponseStream.","For transient service errors: retry the whole request; do not attempt to resume a half-delivered SSE stream after an exception frame."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust handler side\nmatch translate_message(&msg, mode) {\n    Err(TranslateError::UpstreamException { payload_preview }) => {\n        tracing::error!(%payload_preview, \"bedrock mid-stream exception\");\n        // map to 5xx / terminate SSE with an error event; do not resume stream\n        abort_sse_with_error(payload_preview)\n    }\n    r => r,\n}","preventionTips":["Log payload_preview verbatim — it contains the Bedrock exception name that drives the fix (throttle vs access vs model error).","Retry whole requests with exponential backoff for throttling-class exceptions; never resume a partial stream.","Keep per-account streaming concurrency under the Bedrock quota."],"tags":["bedrock","streaming","upstream","sse","rust"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}