Hmbown/CodeWhale · error · Error

Runtime API request failed (${status}): ${message}

Error message

Runtime API request failed (${status}): ${message}

What it means

The Feishu bridge's event pump long-polls GET /v1/threads/<id>/events?since_seq=N; any non-2xx on that stream is wrapped by compactRuntimeError with the runtime's own message. Because this is a polling loop, the error recurs until the underlying condition (auth, thread existence, runtime health) is fixed.

Source

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

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 8880682c63)

Solutions

  1. Recreate the runtime session, update the bridge's token env, restart the bridge
  2. On 404, drop or reset the stored chat mapping (threadStore) so polling stops
  3. Add backoff/reconnect for transient 5xx instead of failing the handler

Example fix

// before
if (!response.ok) {
  const body = await readJsonSafe(response);
  throw new Error(compactRuntimeError(response.status, body));
}

// after - handle the polling-specific cases
if (response.status === 404) {
  await threadStore.deleteChat(chatId);
  return;
}
if (response.status >= 500) {
  await sleep(1000);
  return pollAgain();
}
if (!response.ok) {
  const body = await readJsonSafe(response);
  throw new Error(compactRuntimeError(response.status, body));
}
Defensive patterns

Strategy: retry

Validate before calling

if (!(await threadExists(config, threadId)).exists) {
  await threadStore.deleteChat(chatId);
  return; // stop polling a deleted thread
}

Try / catch

for (let attempt = 0; ; attempt++) {
  try {
    return await pollEvents(threadId, sinceSeq);
  } catch (err) {
    const m = /^Runtime API request failed \((\d+)\)/.exec(err.message);
    if (m && m[1] === '404') { await threadStore.deleteChat(chatId); return; }
    if (m && Number(m[1]) >= 500 && attempt < 5) { await sleep(2 ** attempt * 500); continue; }
    throw err;
  }
}

Prevention

When it happens

Trigger: 401 from stale auth headers after the runtime restarted; 404 when the tracked thread was deleted server-side; 5xx while the runtime shuts down or crash-loops mid-poll.

Common situations: Bridge left running across a runtime restart with an old token; the user deletes the thread in the TUI; runtime under load returning 503.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/0321402ac61c60ee. Report an issue: GitHub.