Hmbown/CodeWhale · error · Error

The update checksum did not match. Your current app is…

Error message

The update checksum did not match. Your current app is unchanged.

What it means

prepareUpdate() verifies both the downloaded byte length and the SHA-256 digest of the archive against the values from the update descriptor (update.size and update.sha256). A mismatch means the downloaded bytes differ from what the release metadata promised, so the archive is discarded and the installed app is left unchanged.

Solutions

  1. Retry the update — a fresh download usually fixes transient truncation or corruption.
  2. Re-run the update check to refresh update.size/sha256 in case the release asset was re-published with different bytes.
  3. Bypass any content-modifying proxy or TLS interceptor and download over a direct connection.
  4. Compare the asset's published SHA-256 on the GitHub release page against update.sha256 to confirm which side is stale.
Defensive patterns

Strategy: retry

Try / catch

try { await prepareUpdate(update); } catch (e) { if (/checksum did not match/.test(e.message)) { /* refresh descriptor via check-for-update and retry once on a stable connection */ } else throw e; }

Prevention

When it happens

Trigger: A truncated or interrupted download (responseBytes returned fewer bytes than update.size); a proxy or middlebox modifying the payload; downloading a re-uploaded asset whose content changed after the descriptor's sha256 was computed; bit-level corruption in transit.

Common situations: Flaky Wi-Fi or mobile connections dropping bytes; corporate TLS proxies that re-compress or inspect content; a release asset replaced in place without updating the published checksum; disk-full conditions during buffering.

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 Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/789131b4b5d07c1f. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/app/updates.mjs:98

  if(position!==end) throw new Error("Invalid update archive length.");
  return count;
}

export async function prepareUpdate(update) {
  if(!update?.available) throw new Error("Check for an available update first.");
  if(!newerVersion(update.version,APP_VERSION)||update.url!==`${repository}/releases/download/v${update.version}/Codewhale-Computer-Use-${update.version}-macos-universal.zip`||!Number.isSafeInteger(update.size)||update.size<=0||update.size>limit) throw new Error("The update identity is invalid.");
  // Only GitHub's fixed release URL and its asset CDN can serve the bytes.
  let url=update.url, response;
  for(let redirects=0;redirects<4;redirects++) {
    response=await fetch(url,{redirect:"manual",signal:AbortSignal.timeout(60_000)});
    if(![301,302,303,307,308].includes(response.status)) break;
    const next=new URL(response.headers.get("location"),url);
    if(next.protocol!=="https:"||!["github.com","release-assets.githubusercontent.com","objects.githubusercontent.com"].includes(next.hostname)) throw new Error("The update download redirected to an unexpected host.");
    url=next.href;
  }
  if(!response?.ok) throw new Error("The update could not be downloaded. Your current app is unchanged.");
  const bytes=await responseBytes(response,update.size);
  if(bytes.length!==update.size||crypto.createHash("sha256").update(bytes).digest("hex")!==update.sha256) throw new Error("The update checksum did not match. Your current app is unchanged.");
  validateReleaseZip(bytes);
  const stage=fs.mkdtempSync(path.join(os.tmpdir(),"codewhale-cu-release-"));
  try {
    const archive=path.join(stage,"release.zip"); fs.writeFileSync(archive,bytes,{mode:0o600});
    const result=spawnSync("ditto",["-x","-k",archive,stage],{encoding:"utf8"});
    if(result.status!==0) throw new Error("The update could not be unpacked.");
    const bundle=path.join(stage,`${APP_NAME}.app`); verifyReleaseBundle(bundle);
    const version=spawnSync("/usr/libexec/PlistBuddy",["-c","Print :CFBundleShortVersionString",path.join(bundle,"Contents","Info.plist")],{encoding:"utf8"});
    if(version.status!==0||version.stdout.trim()!==update.version) throw new Error("The downloaded app has a different version.");
    return {stage,bundle};
  } catch(error) { fs.rmSync(stage,{recursive:true,force:true}); throw error; }
}

export async function restartWithUpdate(prepared,destination) {
  const logDir=path.join(os.homedir(),"Library","Logs",APP_NAME); fs.mkdirSync(logDir,{recursive:true});
  const log=fs.openSync(path.join(logDir,"update.log"),"a",0o600);
  const child=spawn(process.execPath,[fileURLToPath(import.meta.url),"--apply",prepared.bundle,destination,String(process.pid),String(process.ppid)],{detached:true,stdio:["ignore",log,log]});
  try { await new Promise((resolve,reject)=>{child.once("spawn",resolve);child.once("error",reject);}); child.unref(); }

View on GitHub (pinned to 73e0f67d83)