Hmbown/CodeWhale · error · Error

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

Error message

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

What it means

The telegram bridge's event pump fetches `${config.runtimeUrl}/v1/threads/{threadId}/events?since_seq=`; when response.ok is false, readJsonSafe parses the body and compactRuntimeError formats it as `Runtime API request failed (${status}): ${message}`, preferring body.error.message, then body.message, else the raw body. The status and embedded message say exactly what the Codewhale runtime rejected. The runtime URL comes from CODEWHALE_RUNTIME_URL/DEEPSEEK_RUNTIME_URL (default http://127.0.0.1:7878) and auth from CODEWHALE_RUNTIME_TOKEN/DEEPSEEK_RUNTIME_TOKEN.

Source

Thrown at integrations/telegram-bridge/src/index.mjs:614

    }
  };
  const typingTimer = setInterval(() => {
    void tickTyping();
  }, TYPING_INTERVAL_MS);
  typingTimer.unref?.();

  try {
    void tickTyping();
    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 flushLastSeq(false);

      if (turnId && record.turn_id && record.turn_id !== turnId) continue;
      const lifecycleStatus =
        record.event === "turn.lifecycle"
          ? record.payload?.turn?.status || record.payload?.status
          : null;
      const stopTypingEvent =
        record.event === "turn.completed" ||
        ["failed", "canceled", "interrupted"].includes(lifecycleStatus);
      if (typingPaused && record.event !== "approval.required" && !stopTypingEvent) {
        typingPaused = false;

View on GitHub (pinned to 8880682c63)

Solutions

  1. Confirm the runtime is reachable at CODEWHALE_RUNTIME_URL from the bridge host (curl the /v1/threads endpoint with the same auth headers)
  2. Re-sync the token: export CODEWHALE_RUNTIME_TOKEN to the current value the runtime accepts
  3. GET /v1/threads/{threadId} to confirm the thread exists; recreate it if it was pruned
  4. For 5xx, read the runtime's own message in the parentheses and check runtime logs — the bridge is only the messenger

Example fix

# before: runtime moved to port 7879
export CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
# after
export CODEWHALE_RUNTIME_URL=http://127.0.0.1:7879
Defensive patterns

Strategy: retry

Validate before calling

const probe = await fetch(
  `${config.runtimeUrl}/v1/threads/${encodeURIComponent(threadId)}`,
  { headers: authHeaders() }
);
if (!probe.ok) {
  console.error(`runtime preflight failed: ${probe.status}`);
  process.exit(2);
}

Try / catch

try {
  await streamThreadEvents(threadId, sinceSeq);
} catch (error) {
  const status = Number(error.message.match(/Runtime API request failed \((\d+)\)/)?.[1]);
  if (status >= 500) { await backoffAndRetry(); return; }
  if (status === 401 || status === 404) { console.error('config drift — fix token/thread'); process.exit(2); }
  throw error;
}

Prevention

When it happens

Trigger: 401 when the runtime token does not match; 404 when threadId no longer exists on the runtime; 5xx when the runtime behind CODEWHALE_RUNTIME_URL is unhealthy; a wrong port or stale URL in the env.

Common situations: Runtime token rotated in the TUI while the bridge keeps the old env value; runtime moved to a different port; idle chat's thread evicted by retention so the SSE resume 404s.

Related errors


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