midudev/autoskills · critical · Error

bundle hash mismatch

Error message

bundle hash mismatch

What it means

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.

Solutions

  1. 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).
  2. Regenerate the registry manifest (recompute bundleHash and per-file sha256) if you are the registry publisher and legitimately changed files.
  3. Re-sync your local skills-registry.json from the trusted source in case the manifest copy is stale.
  4. Investigate the source branch for unexpected edits if the change was not intentional (possible tampering).

Example fix

// before (registry main branch edited after release)
files fetched from /main/... -> bundle hash mismatch

// after (pin and republish)
# republish: recompute bundleHash in skills-registry.json at tag vX.Y.Z
$ pnpm run registry:publish
Defensive patterns

Strategy: try-catch

Validate before calling

import { createHash, createHmac } from "node:crypto";
function bundleHashOf(files) {
  return createHash("sha256")
    .update(files.map(({ rel, buf }) => `${rel}:${createHash("sha256").update(buf).digest("hex")}`).sort().join("\n")).digest("hex");
}
// verify only after fetching over a trusted channel; never skip the check

Try / catch

try {
  await downloadRegistryEntryToCache(entry, cacheDir);
} catch (e) {
  if (e.message === "bundle hash mismatch") {
    quarantineCache(cacheDir);            // discard unverified content
    throw new Error("Registry content failed integrity check; refusing to install. Re-sync the registry manifest.");
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of midudev/autoskills@0ec725320d (2026-09-15). Data as JSON: /api/errors/5170d0385233d66d. Report an issue: GitHub.

Appendix: source

Thrown at packages/autoskills/installer.ts:347

): Promise<void> {
  const files = [];
  for (const rel of entry.files) {
    files.push({
      rel: normalizeRegistryRelPath(rel),
      ...(await downloadRegistryFile(skillName, entry, rel, opts)),
    });
  }

  const bundleHash = createHash("sha256")
    .update(
      files
        .map(({ rel, buf }) => `${rel}:${sha256Buffer(buf)}`)
        .sort()
        .join("\n"),
    )
    .digest("hex");
  if (bundleHash !== entry.bundleHash) {
    throw new Error("bundle hash mismatch");
  }

  rmSync(destDir, { recursive: true, force: true });
  for (const { rel, buf } of files) {
    const dest = join(destDir, ...rel.split("/"));
    mkdirSync(dirname(dest), { recursive: true });
    writeFileSync(dest, buf);
  }
  opts.onTrace?.(`wrote downloaded bundle to ${destDir}`);
}

function copyRegistryEntryFromLocal(
  skillName: string,
  entry: RegistryEntry,
  destDir: string,
  opts: InstallOptions,
): boolean {
  const registryDir = getInstallRegistryDir(opts);

View on GitHub (pinned to 0ec725320d)