Hmbown/CodeWhale · error · Error

Inconsistent update sizes or compression.

Error message

Inconsistent update sizes or compression.

What it means

The entry's compression method and sizes recorded in the local header must match the central directory: method and general-purpose flags must be identical, and when bit 3 (data descriptor, sizes in a trailing record) is not set, the local header's compressed and uncompressed sizes must equal the central-directory values. Any disagreement means the archive lies about its layout, so the validator cannot bound what `ditto` will write and rejects the update.

Solutions

  1. Repackage the whole archive with a single standard tool (`ditto -c -k` or `zip -r`) so both headers are written by the same writer.
  2. Compare `unzip -v` output (method, sizes) against expectations; fix the packaging step that recompresses individual entries in place.
  3. Avoid streaming zip writers for release artifacts; produce a seekable zip with final sizes in both headers.
  4. Verify the published asset's SHA-256 to rule out partial or tampered downloads.
Defensive patterns

Strategy: validation

Validate before calling

const v = execFileSync("unzip", ["-v", zipPath]).toString();
if (/\n\s*\d+\s+(Bzip2|LZMA|Zstd)/.test(v)) throw new Error("non-deflate method present");

Try / catch

try { validateReleaseZip(bytes); } catch (e) { if (e.message === "Inconsistent update sizes or compression.") throw new Error("Header size/method mismatch — rebuild the archive with one writer"); throw e; }

Prevention

When it happens

Trigger: Streaming zips built with `flags&8` where the local header carries placeholder sizes but the flag isn't set as expected, mismatched method/flags between local and central headers (e.g. recompressed entry without updating one directory), or local sizes differing from central sizes.

Common situations: Recompressing a single entry with a script that edits the local header but not the central one; zip writers producing streaming (data-descriptor) archives consumed by tools that clear the flag; hand-crafted bombs in security testing; different zip library versions writing inconsistent metadata in a build pipeline.

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/b471c368f5d54b78. Report an issue: GitHub.

Appendix: source

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

  const minimum=Math.max(0,bytes.length-65557); let end=-1;
  for(let i=bytes.length-22;i>=minimum;i--) if(bytes.readUInt32LE(i)===0x06054b50&&i+22+bytes.readUInt16LE(i+20)===bytes.length) { end=i; break; }
  if(end<0||bytes.readUInt16LE(end+4)||bytes.readUInt16LE(end+6)) throw new Error("Invalid update archive.");
  const count=bytes.readUInt16LE(end+10); let position=bytes.readUInt32LE(end+16),total=0;
  if(!count||count>2000||bytes.readUInt16LE(end+8)!==count||position+bytes.readUInt32LE(end+12)!==end) throw new Error("Invalid update archive index.");
  const seen=new Set();
  for(let i=0;i<count;i++) {
    if(position+46>end||bytes.readUInt32LE(position)!==0x02014b50) throw new Error("Invalid update entry.");
    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.

View on GitHub (pinned to 73e0f67d83)