can1357/oh-my-pi · error · Error
@oh-my-pi/${pkg}@${version} tarball contained no .node files
Error message
@oh-my-pi/${pkg}@${version} tarball contained no .node files What it means
After extracting a cross-target natives tarball, refreshCrossNatives counts files matching `pi_natives.linux-<arch>*.node`; if none matched it throws, meaning the published tarball is malformed or does not ship natives for the requested architecture. This guards against silently proceeding with no cross natives bundled.
Source
Thrown at packages/metaharness/src/tb/agent.ts:44
* leaf is the artifact real installs load, so it is the ground truth for
* embedding; an unpublished working-tree version fails instead of risking
* loader/API skew.
*/
async function refreshCrossNatives(arches: GuestArch[], version: string): Promise<void> {
for (const arch of arches) {
if (process.platform === "linux" && process.arch === arch) continue;
const pkg = `pi-natives-linux-${arch}`;
const response = await fetch(`https://registry.npmjs.org/@oh-my-pi/${pkg}/-/${pkg}-${version}.tgz`);
if (!response.ok) throw new Error(`Fetching @oh-my-pi/${pkg}@${version} failed: HTTP ${response.status}`);
const archive = new Bun.Archive(await response.arrayBuffer());
let extracted = 0;
for (const [entry, file] of await archive.files()) {
const name = path.posix.basename(entry);
if (!name.startsWith(`pi_natives.linux-${arch}`) || !name.endsWith(".node")) continue;
await Bun.write(path.join(NATIVES_DIR, name), file);
extracted++;
}
if (extracted === 0) throw new Error(`@oh-my-pi/${pkg}@${version} tarball contained no .node files`);
}
}
/** Build and cache self-contained omp binaries for the selected guest architectures. */
export async function prepareAgentBinaries(opts: {
arches: GuestArch[];
cacheDir: string;
rebuild: boolean;
}): Promise<AgentBinaries> {
const manifest = (await Bun.file(path.join(CODING_AGENT_DIR, "package.json")).json()) as { version?: unknown };
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
throw new Error("packages/coding-agent/package.json has no valid version");
}
const arches = [...new Set(opts.arches)];
const cached: Partial<Record<GuestArch, string>> = {};
for (const arch of arches) {
cached[arch] = path.join(opts.cacheDir, `omp-linux-${arch}-${manifest.version}`);View on GitHub (pinned to 9690622007)
Solutions
- Inspect the tarball contents (`tar tzf <pkg>-<version>.tgz`) to see what was actually published
- Republish the platform package with correctly built .node binaries
- If the naming convention changed, update the filter in refreshCrossNatives to match the new names
- Verify you requested a supported GuestArch that the package actually ships
Example fix
// before await refreshCrossNatives(["riscv64"], version); // package doesn't ship riscv64 natives // after const supported: GuestArch[] = ["x64", "arm64"]; await refreshCrossNatives(arches.filter(a => supported.includes(a)), version);
Defensive patterns
Strategy: validation
Validate before calling
// inspect the tarball before relying on it
const res = await fetch(tarballUrl);
const archive = new Bun.Archive(await res.arrayBuffer());
const hasNatives = (await archive.files()).some(([e]) =>
path.posix.basename(e).startsWith(`pi_natives.linux-${arch}`) && e.endsWith(".node"));
if (!hasNatives) throw new Error(`${pkg}@${version} ships no ${arch} natives`); Try / catch
try {
await refreshCrossNatives(arches, version);
} catch (err) {
if (err instanceof Error && err.message.includes("tarball contained no .node files")) {
throw new Error(`bad publish of @oh-my-pi/${pkg}@${version}; pin a known-good version`);
}
throw err;
} Prevention
- Pin known-good versions of the platform packages
- Inspect tarball contents after each upstream publish
- Keep the .node naming filter in sync with the package's file layout
- Only request arches the package actually ships
When it happens
Trigger: The downloaded `@oh-my-pi/pi-natives-linux-<arch>` tarball contained zero `.node` files whose basename starts with `pi_natives.linux-<arch>` — e.g. the package published an empty/placeholder tarball, or the archive layout changed so `.node` files sit elsewhere or under different names.
Common situations: A newly published platform package version shipped without built binaries; npm served a stale/cached empty tarball; native file naming convention changed upstream so the `startsWith` filter no longer matches; wrong arch requested for a package that does not build that target.
Related errors
- Invalid marketplace plugin package name: ${JSON.stringify(na
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
- Invalid pattern: {err}
- Failed to load tree-sitter language: {err}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/f5b8938b1a592bf4.
Report an issue: GitHub.