{"record":{"id":"4392e4a4d92c9e35","repo":"headroomlabs-ai/headroom","slug":"tar-failed-for-tarballpath-result-stderr","errorCode":null,"errorMessage":"tar failed for ${tarballPath}: ${result.stderr || result.stdout || \"unknown error\"}","messagePattern":"tar failed for (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/verify_npm_release_assets.mjs","lineNumber":48,"sourceCode":"\nconst tarballPaths = new Map();\n\nfunction extractPackageJson(tarballPath) {\n  return extractJsonFromTarball(tarballPath, \"package/package.json\");\n}\n\nfunction extractDistPackageJson(tarballPath) {\n  return extractJsonFromTarball(tarballPath, \"package/dist/package.json\");\n}\n\nfunction extractJsonFromTarball(tarballPath, packageJsonPath) {\n  const workdir = mkdtempSync(path.join(tmpdir(), \"headroom-npm-asset-\"));\n  try {\n    const result = spawnSync(\"tar\", [\"-xzf\", tarballPath, \"-C\", workdir], {\n      encoding: \"utf8\",\n    });\n    if (result.status !== 0) {\n      throw new Error(\n        `tar failed for ${tarballPath}: ${result.stderr || result.stdout || \"unknown error\"}`,\n      );\n    }\n    return JSON.parse(readFileSync(path.join(workdir, packageJsonPath), \"utf8\"));\n  } finally {\n    rmSync(workdir, { recursive: true, force: true });\n  }\n}\n\nfunction assertNoFileDependencies(pkg) {\n  for (const field of [\"dependencies\", \"peerDependencies\", \"optionalDependencies\"]) {\n    for (const [name, spec] of Object.entries(pkg[field] || {})) {\n      if (typeof spec === \"string\" && (spec.startsWith(\"file:\") || spec.includes(\"release-assets\"))) {\n        throw new Error(`${pkg.name} has non-portable ${field}.${name} spec: ${spec}`);\n      }\n    }\n  }\n}","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/scripts/verify_npm_release_assets.mjs#L30-L66","documentation":"verify_npm_release_assets.mjs extracts `package/dist/package.json` from each release tarball by spawning `tar -xzf`. A nonzero exit status (or a null status when the `tar` binary cannot be spawned at all) throws with the captured stderr/stdout, or 'unknown error' when neither stream has content. This usually indicates a corrupt tarball or a missing/incompatible system tar.","triggerScenarios":"The tarball is truncated or not gzip data (interrupted `npm pack`, bad download/copy); `tar` is not on PATH (result.status is null, result.error set, message shows 'unknown error'); platform tar incompatibilities (BSD tar vs GNU tar flags); the temp extraction dir is unwritable.","commonSituations":"Verifying artifacts that were transferred between CI and a local machine; minimal Docker images lacking tar; macOS default bsdtar behaving differently from GNU tar; parallel runs colliding on tmp space.","solutions":["Sanity-check the tarball by hand: `tar -tzf <tarball> | head` — if that fails, the archive is corrupt; rebuild it.","Confirm `tar` exists on PATH in the environment running the script (`which tar`); in slim Docker images install it (`apk add tar` / `apt-get install tar`).","On macOS/BSD-tar issues, run under GNU tar (`gtar`) or the Node implementation the script could use (e.g., node-tar) if portable.","If status is null, also log `result.error` — spawnSync puts the spawn failure there, not in stderr."],"exampleFix":"// before\nconst result = spawnSync(\"tar\", [\"-xzf\", tarballPath, \"-C\", workdir], { encoding: \"utf8\" });\nif (result.status !== 0) {\n  throw new Error(`tar failed for ${tarballPath}: ${result.stderr || result.stdout || \"unknown error\"}`);\n}\n\n// after: surface the spawn error too\nif (result.error || result.status !== 0) {\n  throw new Error(\n    `tar failed for ${tarballPath}: ${result.error?.message || result.stderr || result.stdout || \"unknown error\"}`,\n  );\n}","handlingStrategy":"try-catch","validationCode":"import { execFileSync } from \"node:child_process\";\n\nfunction tarballIntact(tarballPath) {\n  try {\n    execFileSync(\"tar\", [\"-tzf\", tarballPath], { stdio: \"ignore\" });\n    return true;\n  } catch {\n    return false;\n  }\n}\n// assert tarballIntact(p) before extractJsonFromTarball(p, ...)","typeGuard":null,"tryCatchPattern":"try {\n  const pkg = extractJsonFromTarball(tarballPath, \"package/dist/package.json\");\n} catch (err) {\n  if (/tar failed/.test(err.message)) {\n    // distinguish corrupt archive vs missing binary before retrying\n    if (!tarballIntact(tarballPath)) throw new Error(`corrupt tarball: ${tarballPath}`);\n    throw new Error(`tar unavailable or incompatible: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Verify tarballs with `tar -tzf` right after producing or downloading them.","Ensure `tar` is installed in the runtime image (slim Docker images often omit it).","Log `result.error` from spawnSync alongside stderr — spawn failures land there, not in the streams."],"tags":["npm","tar","verification","release","spawn"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}