{"record":{"id":"5170d0385233d66d","repo":"midudev/autoskills","slug":"bundle-hash-mismatch","errorCode":null,"errorMessage":"bundle hash mismatch","messagePattern":"bundle hash mismatch","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/autoskills/installer.ts","lineNumber":347,"sourceCode":"): Promise<void> {\n  const files = [];\n  for (const rel of entry.files) {\n    files.push({\n      rel: normalizeRegistryRelPath(rel),\n      ...(await downloadRegistryFile(skillName, entry, rel, opts)),\n    });\n  }\n\n  const bundleHash = createHash(\"sha256\")\n    .update(\n      files\n        .map(({ rel, buf }) => `${rel}:${sha256Buffer(buf)}`)\n        .sort()\n        .join(\"\\n\"),\n    )\n    .digest(\"hex\");\n  if (bundleHash !== entry.bundleHash) {\n    throw new Error(\"bundle hash mismatch\");\n  }\n\n  rmSync(destDir, { recursive: true, force: true });\n  for (const { rel, buf } of files) {\n    const dest = join(destDir, ...rel.split(\"/\"));\n    mkdirSync(dirname(dest), { recursive: true });\n    writeFileSync(dest, buf);\n  }\n  opts.onTrace?.(`wrote downloaded bundle to ${destDir}`);\n}\n\nfunction copyRegistryEntryFromLocal(\n  skillName: string,\n  entry: RegistryEntry,\n  destDir: string,\n  opts: InstallOptions,\n): boolean {\n  const registryDir = getInstallRegistryDir(opts);","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/midudev/autoskills/blob/0ec725320d2137253ab2e68e7ba8a072148e741a/packages/autoskills/installer.ts#L329-L365","documentation":"After downloading all files of a registry entry, the installer computes a deterministic bundle hash from each file's relative path and content hash, and compares it to the bundleHash published in the registry entry. A mismatch means the downloaded content differs from what the registry publisher hashed — either the files changed on the source branch or the manifest hash is stale/wrong.","triggerScenarios":"downloadRegistryEntry downloads files from the mutable 'main' fallback URL (or any base whose content differs from the pinned version), producing a bundle hash different from entry.bundleHash in the registry manifest.","commonSituations":"Downloading from the /main/ fallback after files were edited upstream but the registry manifest wasn't regenerated; a partially updated registry release; tampered registry or man-in-the-middle-modified content; hand-edited skill files upstream.","solutions":["Re-run the install to fetch from the version-pinned URL rather than the 'main' fallback (ensure the package version resolves and the version tag exists upstream).","Regenerate the registry manifest (recompute bundleHash and per-file sha256) if you are the registry publisher and legitimately changed files.","Re-sync your local skills-registry.json from the trusted source in case the manifest copy is stale.","Investigate the source branch for unexpected edits if the change was not intentional (possible tampering)."],"exampleFix":"// before (registry main branch edited after release)\nfiles fetched from /main/... -> bundle hash mismatch\n\n// after (pin and republish)\n# republish: recompute bundleHash in skills-registry.json at tag vX.Y.Z\n$ pnpm run registry:publish","handlingStrategy":"try-catch","validationCode":"import { createHash, createHmac } from \"node:crypto\";\nfunction bundleHashOf(files) {\n  return createHash(\"sha256\")\n    .update(files.map(({ rel, buf }) => `${rel}:${createHash(\"sha256\").update(buf).digest(\"hex\")}`).sort().join(\"\\n\")).digest(\"hex\");\n}\n// verify only after fetching over a trusted channel; never skip the check","typeGuard":null,"tryCatchPattern":"try {\n  await downloadRegistryEntryToCache(entry, cacheDir);\n} catch (e) {\n  if (e.message === \"bundle hash mismatch\") {\n    quarantineCache(cacheDir);            // discard unverified content\n    throw new Error(\"Registry content failed integrity check; refusing to install. Re-sync the registry manifest.\");\n  } else throw e;\n}","preventionTips":["Install from version-pinned registry URLs instead of the mutable main fallback.","Regenerate bundleHash with the official publish script whenever upstream files change.","Re-sync the local manifest before installs; never disable the hash check.","Investigate upstream edits when a mismatch appears unexpectedly — it can indicate tampering."],"tags":["integrity","checksum","tampering","registry"],"backgroundTag":"checksum-mismatch","analyzedSha":"0ec725320d2137253ab2e68e7ba8a072148e741a","analyzedAt":"2026-09-15T14:12:31.090Z","contentChangedAt":"2026-09-15T14:12:31.090Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}