Hmbown/CodeWhale · error · Error

Invalid or oversized compressed update entry.

Error message

Invalid or oversized compressed update entry.

What it means

The validator actually inflates each entry's compressed payload with `inflateRawSync`, capped at the declared uncompressed size via maxOutputLength. If zlib throws — corrupt deflate stream, or output exceeding the declared cap — the entry is either not a valid deflate stream or a decompression bomb that understates its true expansion, and the update is rejected before anything is written to disk.

Solutions

  1. Re-download the release asset and verify its SHA-256 against the digest from checkForUpdate/releaseUpdate before validating.
  2. Run `unzip -t` locally — it will report CRC/inflate errors on the same entries.
  3. Rebuild and re-upload the release artifact; the published bytes themselves are bad.
  4. If you build archives yourself, confirm every entry is deflate (or stored) with sizes matching actual content, e.g. round-trip `zip`/`unzip -t` in CI.

Example fix

// before
const bytes = fs.readFileSync(zipPath);
validateReleaseZip(bytes);
// after
const bytes = fs.readFileSync(zipPath);
const digest = crypto.createHash("sha256").update(bytes).digest("hex");
if (digest !== update.sha256) throw new Error("Downloaded update is corrupt — retry the download.");
validateReleaseZip(bytes);
Defensive patterns

Strategy: validation

Validate before calling

const bytes = fs.readFileSync(zipPath);
const digest = crypto.createHash("sha256").update(bytes).digest("hex");
if (update.sha256 && digest !== update.sha256) throw new Error("Corrupt download — retry");

Try / catch

try { validateReleaseZip(bytes); } catch (e) { if (e.message === "Invalid or oversized compressed update entry.") throw new Error("Entry fails to inflate within its declared size — re-download or republish"); throw e; }

Prevention

When it happens

Trigger: An entry with method 8 whose payload is not valid raw-deflate data, or whose inflated size exceeds the declared `size` (maxOutputLength = size), or method 0 where the stored payload length equals 0 making expansion undecidable — common with truncated files, zip-bomb attempts, or wrong method recorded in the headers.

Common situations: A zip bomb where headers declare a tiny size but the stream expands hugely; truncated or bit-rotted downloads; archives where a stored (method 0) entry was declared as deflate; fuzzed/corrupted assets on disk.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/ae1f6f8f1b8c989e. Report an issue: GitHub.

Appendix: source

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

    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.
  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)