{"record":{"id":"580e280b0c6d5969","repo":"can1357/oh-my-pi","slug":"embedded-addon-archive-missing-pending-keys","errorCode":null,"errorMessage":"Embedded addon archive missing: ${[...pending.keys()].join(\", \")}","messagePattern":"Embedded addon archive missing: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/natives/native/loader-state.js","lineNumber":548,"sourceCode":"\t\t\tthrow new Error(`Unsupported embedded addon archive entry type ${typeflag}: ${filename}`);\n\t\t}\n\n\t\tconst file = pending.get(filename);\n\t\tif (file) {\n\t\t\tif (typeof file.size === \"number\" && file.size !== size) {\n\t\t\t\tthrow new Error(`Embedded addon size mismatch for ${filename}: expected ${file.size}, got ${size}`);\n\t\t\t}\n\t\t\tconst targetPath = path.join(targetDir, filename);\n\t\t\twriteEmbeddedAddonFile(targetPath, archive.subarray(offset, offset + size));\n\t\t\tpending.delete(filename);\n\t\t\twrittenPaths.push(targetPath);\n\t\t}\n\n\t\toffset += Math.ceil(size / 512) * 512;\n\t}\n\n\tif (pending.size > 0) {\n\t\tthrow new Error(`Embedded addon archive missing: ${[...pending.keys()].join(\", \")}`);\n\t}\n\n\treturn writtenPaths;\n}\n\nfunction maybeExtractEmbeddedAddon(ctx, errors) {\n\tif (!ctx.isCompiledBinary || !embeddedAddon) return null;\n\tif (embeddedAddon.platformTag !== ctx.platformTag || embeddedAddon.version !== ctx.packageVersion) return null;\n\n\tconst selectedEmbeddedFile = selectEmbeddedAddonFile(ctx.selectedVariant);\n\tif (!selectedEmbeddedFile) return null;\n\tconst targetPath = path.join(ctx.versionedDir, selectedEmbeddedFile.filename);\n\n\tstartupMarker(\"native:extractEmbeddedAddon:start\");\n\ttry {\n\t\tprepareNativeVersionDir(ctx.versionedDir);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);","sourceCodeStart":530,"sourceCodeEnd":566,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/natives/native/loader-state.js#L530-L566","documentation":"After walking the entire tar stream, the loader verifies that every manifest file it decided to extract (files absent or stale on disk) was actually present in the archive. If any pending filenames were never seen, it throws listing the missing names rather than leaving a partial extraction that would fail at require() time.","triggerScenarios":"extractEmbeddedAddonArchive completes the tar walk but pending still contains entries — i.e. the manifest listed files (e.g. 'pi_natives-darwin-arm64.node') that do not exist as entries in the embedded .tar.gz.","commonSituations":"A build packaged an archive subset while baking the full file manifest; the archive was truncated such that trailing entries were never reached (tar usually ends at the first zero block); manifest filenames differ from archive entry names (path or case mismatch).","solutions":["Reinstall omp / rebuild the binary so the embedded manifest and archive match.","Verify the archive contents with tar -tzf against the manifest's filenames.","Fix the packaging script to derive the manifest from the actual files placed into the tar.","Check for filename case or directory-prefix mismatches between manifest and archive entries."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { execFileSync } from \"node:child_process\";\nfunction assertArchiveCoversManifest(archivePath, files) {\n  const entries = new Set(\n    execFileSync(\"tar\", [\"-tzf\", archivePath], { encoding: \"utf8\" }).split(\"\\n\").filter(Boolean)\n  );\n  const missing = files.map(f => f.filename).filter(name => !entries.has(name));\n  if (missing.length) throw new Error(`Embedded addon archive missing: ${missing.join(\", \")}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  extractEmbeddedAddonArchive({ archivePath, files, targetDir });\n} catch (err) {\n  if (err.message.startsWith(\"Embedded addon archive missing:\")) {\n    // treat install as broken; prompt user to reinstall\n  } else throw err;\n}","preventionTips":["Derive the manifest from the tar contents (or vice versa) in one packaging step.","Assert tar -tzf output covers every manifest filename in CI.","Bump/check the archive-vs-manifest pairing whenever either is regenerated."],"tags":["archive","integrity","missing-file","native-addon"],"backgroundTag":"archive-entry-missing","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}