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
- Re-fetch the job list so raw_log_url values are fresh, then retry
- Verify the token with bk whoami or a direct curl to api.buildkite.com
- On 429: reduce worker concurrency or wait — cached logs make reruns cheap
- 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
- Always re-list jobs right before fetching logs — never persist raw_log_url
- Keep the <job.id>.log cache so reruns are idempotent and cheap
- Cap concurrent log fetches to stay under BuildKite rate limits
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
- not called
- BUILDKITE_AGENT_TOKEN not set and no existing buildkite-agen
- Buildkite token not found: set BUILDKITE_AGENT_TOKEN or gran
- [azure] Failed to get public IP for ${vmName}
- github ${path}: ${res.status}
AI-assisted analysis of oven-sh/bun@8c5296ac45 (2026-08-16).
Data as JSON: /api/errors/bbe25a10381350c5.
Report an issue: GitHub.