Hmbown/CodeWhale · error · Error
The update entry size did not match its contents.
Error message
The update entry size did not match its contents.
What it means
After successfully inflating an entry, the validator compares the actual expanded byte count with the size declared in the central directory (`expanded!==size`). A mismatch means the archive's declared metadata doesn't describe its real content, so downstream layout math and the extraction step would operate on wrong sizes — treated as integrity failure and rejected.
Solutions
- Rebuild the archive from source with a standard zip tool; never patch entries inside an existing zip.
- Verify with `unzip -t` (CRC check catches the same inconsistency) before publishing.
- Re-download and SHA-256-verify the asset to rule out a corrupt transfer.
- Ensure the packaging pipeline writes sizes last (after content is final) — regenerate the zip whenever any entry content changes.
Defensive patterns
Strategy: validation
Validate before calling
execFileSync("unzip", ["-t", zipPath], {stdio:"inherit"}); Try / catch
try { validateReleaseZip(bytes); } catch (e) { if (e.message === "The update entry size did not match its contents.") throw new Error("Declared size ≠ inflated size — rebuild the artifact"); throw e; } Prevention
- Never patch file contents inside an existing zip
- Regenerate the archive whenever any entry changes
- CRC-check with `unzip -t` in the release pipeline
- SHA-256-verify the downloaded asset
When it happens
Trigger: An entry whose deflate stream decodes to a byte count different from the central-directory `size` field — e.g. sizes written for a pre-compression variant of the file, an archive edited after creation, or stored (method 0) entries whose payload length differs from the declared size.
Common situations: Build scripts patching file contents inside a finished zip without updating the central directory; tools writing data descriptors while central sizes were copied from a template; tampered or maliciously crafted updates; partial re-zipping of an artifact.
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
- Inconsistent update file header.
- Invalid update archive length.
- Invalid update entry.
- Invalid update file header.
- Inconsistent update sizes or compression.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f4e2f4c84bcd79d7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/app/updates.mjs:77
const flags=bytes.readUInt16LE(position+8),method=bytes.readUInt16LE(position+10),length=bytes.readUInt16LE(position+28),extra=bytes.readUInt16LE(position+30),comment=bytes.readUInt16LE(position+32);
const name=bytes.subarray(position+46,position+46+length).toString("utf8");
const kind=(bytes.readUInt32LE(position+38)>>>16)&0xf000,offset=bytes.readUInt32LE(position+42),compressed=bytes.readUInt32LE(position+20);
const size=bytes.readUInt32LE(position+24); total+=size;
if(flags&1||![0,8].includes(method)||![0,0x4000,0x8000].includes(kind)||total>512*1024*1024||position+46+length+extra+comment>end) throw new Error("Unsupported update entry.");
if(!name.startsWith(`${APP_NAME}.app/`)||name.includes("\\")||name.includes(":")||name.includes("\0")||name.split("/").some(part=>part===".."||part===".")||seen.has(name)) throw new Error("Unsafe update path.");
seen.add(name);
if(offset+30>position||bytes.readUInt32LE(offset)!==0x04034b50) throw new Error("Invalid update file header.");
const localLength=bytes.readUInt16LE(offset+26),localExtra=bytes.readUInt16LE(offset+28);
if(offset+30+localLength+localExtra+compressed>bytes.readUInt32LE(end+16)||bytes.subarray(offset+30,offset+30+localLength).toString("utf8")!==name) throw new Error("Inconsistent update file header.");
if(bytes.readUInt16LE(offset+8)!==method||bytes.readUInt16LE(offset+6)!==flags||(!(flags&8)&&(bytes.readUInt32LE(offset+18)!==compressed||bytes.readUInt32LE(offset+22)!==size))) throw new Error("Inconsistent update sizes or compression.");
const start=offset+30+localLength+localExtra;
// Header sizes are untrusted. Bound actual expansion before ditto writes
// anything, including a compressed payload whose headers understate size.
const payload=bytes.subarray(start,start+compressed);
let expanded;
try { expanded=method===0?payload.length:inflateRawSync(payload,{maxOutputLength:Math.max(size,1)}).length; }
catch { throw new Error("Invalid or oversized compressed update entry."); }
if(expanded!==size) throw new Error("The update entry size did not match its contents.");
position+=46+length+extra+comment;
}
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;
}View on GitHub (pinned to 73e0f67d83)