oven-sh/bun · error · Error

${res.status} ${job.raw_log_url}

Error message

${res.status} ${job.raw_log_url}

What it means

fetchLog() in scripts/ci-slowest-tests.ts downloads a BuildKite job log via job.raw_log_url with a bearer token (deliberately avoiding `bk job log`, which hangs on some Windows/alpine jobs). It throws when that fetch returns non-2xx; successful downloads are cached as <job.id>.log files under the cache dir.

Source

Thrown at scripts/ci-slowest-tests.ts:106

        .replace(/\s+\x1b\[90m\[attempt #\d+\]\x1b\[0m\r*$/, "")
        .replace(/\r+$/, "")
        .replace(/\\/g, "/")
        .trim();
      out.set(clean, (out.get(clean) ?? 0) + (ts - curStart));
    }
    curStart = ts;
    curName = m[2].replace(/\r+$/, "").replace(/\\/g, "/").trim();
  }
  return out;
}

// Do NOT use `bk job log` — it hangs indefinitely on some Windows/alpine jobs.
// Fetching raw_log_url directly with the token works for all of them.
async function fetchLog(job: Job): Promise<string> {
  const path = join(CACHE, `${job.id}.log`);
  if (existsSync(path)) return readFileSync(path, "utf8");
  const res = await fetch(job.raw_log_url, { headers: { Authorization: `Bearer ${TOKEN}` } });
  if (!res.ok) throw new Error(`${res.status} ${job.raw_log_url}`);
  const out = await res.text();
  writeFileSync(path, out);
  return out;
}

type Agg = { maxMs: number; maxPlat: string; perPlat: Map<string, number> };
const agg = new Map<string, Agg>();

let done = 0;
const queue = [...jobs];
async function worker() {
  for (;;) {
    const job = queue.shift();
    if (!job) return;
    try {
      const log = await fetchLog(job);
      const plat = platOf(job.name);
      for (const [file, ms] of parseLog(log)) {

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Re-fetch the job list so raw_log_url values are fresh, then retry
  2. Verify the token with bk whoami or a direct curl to api.buildkite.com
  3. On 429: reduce worker concurrency or wait — cached logs make reruns cheap
  4. If logs were GC'd, target a more recent build

Example fix

# before
$ bun scripts/ci-slowest-tests.ts
Error: 403 https://buildkite.com/.../log.txt

# after — fresh token, then re-run so job list (and log URLs) refresh
$ export BUILDKITE_API_TOKEN=<fresh read token>
$ bun scripts/ci-slowest-tests.ts
Defensive patterns

Strategy: retry

Validate before calling

const r = await fetch("https://api.buildkite.com/v2/user", { headers: { Authorization: `Bearer ${TOKEN}` } });
if (!r.ok) throw new Error(`token invalid: ${r.status}`);

Type guard

const isLogFetchError = (e: unknown) =>
  e instanceof Error && /^\d{3} http/.test(e.message);

Try / catch

for (let i = 1; ; i++) {
  try { return await fetchLog(job); }
  catch (e) {
    if (!isLogFetchError(e) || i === 3) throw e;
    if (e.message.startsWith("401 ")) throw e; // auth will not fix itself
    await new Promise(r => setTimeout(r, i * 2000));
  }
}

Prevention

When it happens

Trigger: An expired or rotated raw_log_url (BuildKite log links age out), 401 from an invalid or insufficient TOKEN, 429 when fanning out over many jobs, or logs garbage-collected for old builds.

Common situations: Re-running the script against an old build whose log links expired; BUILDKITE_API_TOKEN unset or stale; large builds where parallel log fetches trip rate limits.

Related errors


AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16). Data as JSON: /api/errors/bbe25a10381350c5. Report an issue: GitHub.