Hmbown/CodeWhale · error

${compactRuntimeError(response.status, body)}

Error message

${compactRuntimeError(response.status, body)}

What it means

streamTurnEvents in the Feishu bridge throws when the HTTP response that should carry the Server-Sent-Events turn stream has a non-OK status. The body is read with readJsonSafe and formatted via compactRuntimeError, so the message carries the Runtime's status and error payload.

Solutions

  1. Check the status and body in the thrown message: fix auth (re-derive tokens), URL, or payload accordingly.
  2. Verify the bridge's runtime base URL and auth env vars match the currently running `codewhale web` instance.
  3. Inspect Runtime logs for the 5xx cause if the status is a server error, and check the turn request body for malformed ids.

Example fix

// before: stale session headers after Runtime restart
headers: authHeaders()
// after: restart bridge alongside the Runtime so credentials regenerate
// $ codewhale web && npm run start --prefix integrations/feishu-bridge
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(streamUrl, { method: 'POST', headers: authHeaders() });
if (!res.ok) throw new Error(await res.text());
if (!res.headers.get('content-type')?.includes('text/event-stream')) throw new Error('not an SSE stream');

Type guard

function isRuntimeHttpError(err) {
  return err instanceof Error && /\b\d{3}\b/.test(err.message);
}

Try / catch

try {
  await streamTurnEvents(payload);
} catch (e) {
  console.error('[feishu] turn stream failed:', e.message);
  await replyToFeishu('Backend error: ' + e.message.slice(0, 200));
}

Prevention

When it happens

Trigger: Calling streamTurnEvents when the Runtime rejects the turn-streaming request — 401 from stale auth headers, 404 from a wrong RUNTIME_URL path, 400 from an invalid turn payload, or 5xx from a Runtime crash.

Common situations: Feishu bridge env credentials out of sync with the running Runtime after a restart; wrong base URL/port in the bridge env; Runtime rejecting a malformed conversation/turn id built from a Feishu message.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/890ed6936fabbbcb. Report an issue: GitHub.

Appendix: source

Thrown at integrations/feishu-bridge/src/index.mjs:333

async function streamTurnEvents(chatId, threadId, turnId, sinceSeq) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), config.turnTimeoutMs);
  let responseText = "";
  let latestSeq = sinceSeq;
  let sentProgressAt = Date.now();

  try {
    const response = await fetch(
      `${config.runtimeUrl}/v1/threads/${encodeURIComponent(threadId)}/events?since_seq=${sinceSeq}`,
      {
        headers: authHeaders(),
        signal: controller.signal
      }
    );
    if (!response.ok) {
      const body = await readJsonSafe(response);
      throw new Error(compactRuntimeError(response.status, body));
    }

    for await (const event of readSse(response)) {
      if (!event.data) continue;
      const record = JSON.parse(event.data);
      latestSeq = Math.max(latestSeq, Number(record.seq || 0));
      await threadStore.patchChat(chatId, { lastSeq: latestSeq });

      if (turnId && record.turn_id && record.turn_id !== turnId) continue;

      if (record.event === "item.delta" && record.payload?.kind === "agent_message") {
        responseText += record.payload.delta || "";
        const now = Date.now();
        if (responseText.length > config.maxReplyChars && now - sentProgressAt > 15000) {
          await sendText(chatId, responseText.slice(0, config.maxReplyChars));
          responseText = responseText.slice(config.maxReplyChars);
          sentProgressAt = now;
        }

View on GitHub (pinned to 433685b202)