oven-sh/bun · error · Error

github ${p}: ${r.status} ${r.statusText}

Error message

github ${p}: ${r.status} ${r.statusText}

What it means

The gh() helper inside compareGithubReleases() (local mode of scripts/binary-size.ts) throws when fetching releases/latest or releases/tags/canary from api.github.com returns non-2xx. status + statusText distinguish rate limiting (403) from a missing release (404).

Source

Thrown at scripts/binary-size.ts:266

function fmtBytes(n: number): string {
  return `${(n / 1024 / 1024).toFixed(2)} MB`;
}
function fmtDelta(n: number): string {
  const sign = n >= 0 ? "+" : "-";
  const abs = Math.abs(n);
  return abs >= 1024 * 1024 ? `${sign}${(abs / 1024 / 1024).toFixed(2)} MB` : `${sign}${(abs / 1024).toFixed(1)} KB`;
}

// ─── local mode: canary vs latest tagged release ───

type GithubRelease = { tag_name: string; assets: { name: string; browser_download_url: string }[] };

async function compareGithubReleases() {
  const auth = process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : undefined;
  const gh = (p: string) =>
    fetch(`https://api.github.com/repos/oven-sh/bun/${p}`, { headers: auth }).then(r => {
      if (!r.ok) throw new Error(`github ${p}: ${r.status} ${r.statusText}`);
      return r.json() as Promise<GithubRelease>;
    });

  const [latest, canary] = await Promise.all([gh("releases/latest"), gh("releases/tags/canary")]);

  // The release zips we care about are the stripped runtime binaries:
  // bun-<os>-<arch>[-musl][-baseline].zip. Skip -profile (unstripped) and
  // anything that isn't a single-binary zip.
  const isBinaryZip = (n: string) => /^bun-[a-z0-9-]+\.zip$/.test(n) && !n.includes("-profile");
  const assetMap = (r: GithubRelease) =>
    new Map(r.assets.filter(a => isBinaryZip(a.name)).map(a => [a.name.replace(/\.zip$/, ""), a.browser_download_url]));

  const latestAssets = assetMap(latest);
  const canaryAssets = assetMap(canary);
  const triplets = [...latestAssets.keys()].filter(t => canaryAssets.has(t)).sort();

  process.stderr.write(`Reading ${triplets.length} zips from each of ${latest.tag_name} and canary…\n`);
  const [latestSizes, canarySizes] = await Promise.all([

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. export GITHUB_TOKEN=$(gh auth token) before running
  2. On 404 for canary: wait for release automation to finish publishing and re-run
  3. On 403: wait out the rate-limit window or authenticate

Example fix

# before
$ bun scripts/binary-size.ts
Error: github releases/tags/canary: 404

# after
$ export GITHUB_TOKEN=$(gh auth token)
$ bun scripts/binary-size.ts
Defensive patterns

Strategy: retry

Validate before calling

if (!process.env.GITHUB_TOKEN) {
  console.warn("no GITHUB_TOKEN — anonymous GitHub rate limit is 60/h, gh() may fail");
}

Try / catch

try {
  await compareGithubReleases();
} catch (e) {
  if (/github releases\/.*: 404/.test(String(e?.message))) {
    console.error("canary/latest release missing — publish may be in flight, retry shortly");
  }
  throw e;
}

Prevention

When it happens

Trigger: No GITHUB_TOKEN in the local environment with the anonymous 60 req/h quota exhausted (403); the canary tag or latest release temporarily absent while release automation is mid-publish (404); GitHub 5xx.

Common situations: Running bun scripts/binary-size.ts locally for a canary-vs-latest comparison without auth; running exactly while the release pipeline is publishing and tags are in flux.

Related errors


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