can1357/oh-my-pi · error · BedrockApiError

code

code

Error message

${code}: ${errorMessage}

What it means

Bedrock event streams can carry :message-type "error" frames carrying an :error-code and :error-message (or raw payload). The library raises a BedrockApiError formatted "<code>: <message>" with the error code attached, using status 400 since the stream already started successfully.

Source

Thrown at packages/ai/src/providers/amazon-bedrock.ts:513

			}
			if (!response.body) throw new AIError.BedrockApiError("Bedrock response has no body", response.status);

			// Track first event for the abort/diagnostic path (currently informational).
			for await (const message of decodeEventStream(response.body)) {
				const messageType = message.headers[":message-type"];
				const eventType = message.headers[":event-type"];

				if (messageType === "exception") {
					const exceptionType = message.headers[":exception-type"] || "Exception";
					const payload = safeParsePayload(message.payload) as { message?: string } | undefined;
					const errorMessage = payload?.message || new TextDecoder().decode(message.payload);
					const text = `${exceptionType}: ${errorMessage}`;
					throw new AIError.BedrockApiError(text, 400, { code: exceptionType });
				}
				if (messageType === "error") {
					const code = message.headers[":error-code"] || "UnknownError";
					const errorMessage = message.headers[":error-message"] || new TextDecoder().decode(message.payload);
					throw new AIError.BedrockApiError(`${code}: ${errorMessage}`, 400, { code });
				}
				if (messageType !== "event") continue;

				const payload = safeParsePayload(message.payload);
				if (!payload) continue;

				switch (eventType) {
					case "messageStart": {
						// no-op: first event marker is implicit by stream entry.
						const ev = payload as MessageStartEvent;
						if (ev.role !== "assistant") {
							throw new AIError.BedrockApiError(
								"Unexpected assistant message start but got user message start instead",
								0,
							);
						}
						stream.push({ type: "start", partial: output });
						break;

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the attached code to classify: retryable service errors → retry with backoff
  2. Inspect the message body for AWS's diagnostic detail
  3. Check the AWS health dashboard if failures cluster in time
  4. Capture and report persistent error codes to AWS support with request ids

Example fix

// before
for await (const chunk of stream) { use(chunk); } // throws mid-iteration
// after
try {
  for await (const chunk of stream) use(chunk);
} catch (e) {
  if (e.code && e.status === 400) logBedrockStreamError(e.code, e.message);
  throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

function isBedrockStreamError(e: unknown): e is AIError.BedrockApiError & { code: string } {
  return e instanceof AIError.BedrockApiError && typeof (e as { code?: unknown }).code === "string" && e.status === 400;
}

Try / catch

try {
  for await (const ev of streamBedrock(options)) consume(ev);
} catch (e) {
  if (isBedrockStreamError(e)) {
    log.warn("bedrock in-stream error", { code: e.code, message: e.message });
    if (RETRYABLE.has(e.code)) return retryWithBackoff();
  }
  throw e;
}

Prevention

When it happens

Trigger: In-stream error frames sent by Bedrock or the event-stream encoder (e.g. service unavailable mid-stream, deserialization failures, internal errors after the 200 response).

Common situations: Model infrastructure failures during generation; networking middleboxes injecting error frames; account-level service events interrupting streams.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/fde4b2c48437c868. Report an issue: GitHub.