oven-sh/bun · error · Error

${nm} failed on ${bunProfile} ${r.stderr}

Error message

${nm} failed on ${bunProfile}
${r.stderr}

What it means

While reading the unstripped bun-profile binary's symbol table, nm exited non-zero and its stderr is appended to the message. This is a tool/format-level failure — nm ran but refused or failed to parse the file — distinct from spawn errors (167) and from finding zero symbols (169).

Source

Thrown at scripts/orderfile/generate.ts:141

  /** Fail if fewer than this many functions were traced. */
  minFunctions?: number;
  /** Print per-workload progress. */
  verbose?: boolean;
}

/**
 * Linker-visible function names, by address. Multiple names can share one
 * address (aliases, and ICF on darwin), and the order file must list every name
 * the linker might know a function by. On macOS nm prints names with the C
 * leading underscore, which is also what `-order_file` expects, so no
 * stripping — lld and ld take exactly what nm gave.
 */
function readSymbolTable(bunProfile: string): Map<number, string[]> {
  // Bare `nm` with no GNU-only long options: the regex below is the
  // defined-text-symbol filter, and nothing here depends on output order.
  const nm = process.env.NM || "nm";
  const r = runCommand([nm, bunProfile]);
  if (r.status !== 0) throw new Error(`${nm} failed on ${bunProfile}\n${r.stderr}`);

  const symbols = new Map<number, string[]>();
  for (const line of r.stdout.toString().split("\n")) {
    const m = /^([0-9a-f]+) ([tT]) (\S+)$/.exec(line);
    if (!m) continue;
    const address = parseInt(m[1]!, 16);
    const names = symbols.get(address);
    if (names) names.push(m[3]!);
    else symbols.set(address, [m[3]!]);
  }
  if (symbols.size === 0) throw new Error(`${nm} reported no text symbols — is ${bunProfile} stripped?`);
  return symbols;
}

/** Write function starts for functrace.c: u64 magic, version, count, addresses. */
function writeStarts(path: string, addresses: number[]): void {
  const buffer = new ArrayBuffer((STARTS_HEADER_WORDS + addresses.length) * 8);
  const words = new BigUint64Array(buffer);

View on GitHub (pinned to 8c5296ac45)

Solutions

  1. Read the appended stderr — it names the parse problem
  2. Check the file: `file <buildDir>/bun-profile` and confirm it matches the host format
  3. Point NM at the platform-correct nm or unset the NM override so the default is used
  4. Rebuild the binary (`bun run build:release`) if the file is corrupt or truncated

Example fix

# before
$ NM=llvm-nm-15 bun scripts/orderfile/generate.ts
Error: llvm-nm-15 failed on build/bun-profile
...

# after — use the default platform nm
$ env -u NM bun scripts/orderfile/generate.ts
Defensive patterns

Strategy: try-catch

Validate before calling

import { spawnSync } from "node:child_process";

const probe = spawnSync(process.env.NM || "nm", ["--version"]);
if (probe.error || probe.status !== 0) {
  throw new Error("nm is missing or broken — fix NM/PATH before generating the order file");
}

Try / catch

try {
  return readSymbolTable(bunProfile);
} catch (err) {
  if (/failed on .*\n/.test(String(err))) {
    console.error(`nm could not parse ${bunProfile} — verify format/arch or set NM`);
  }
  throw err;
}

Prevention

When it happens

Trigger: The NM env var (or bare `nm`) resolves to an nm that cannot parse the binary's object format (e.g. LLVM nm vs GNU nm disagreements, wrong-arch nm); the binary is truncated or corrupt from an interrupted build.

Common situations: Cross-compilation environments where the default nm targets another platform; Nix/conda shims shadowing binutils nm; partial build artifacts after a cancelled `bun run build:release`.

Related errors


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