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
- Re-download the release asset and verify its SHA-256 against the digest from checkForUpdate/releaseUpdate before validating.
- Run `unzip -t` locally — it will report CRC/inflate errors on the same entries.
- Rebuild and re-upload the release artifact; the published bytes themselves are bad.
- 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
- Always verify SHA-256 of downloaded assets before validating
- Run `unzip -t` (CRC check) before publishing
- Design packaging so declared sizes equal real content
- Retry the download on any validation failure — corrupted transfers are the most common cause
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
- Inconsistent update file header.
- Invalid update archive length.
- Invalid update entry.
- Invalid update file header.
- The update entry size did not match its contents.
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)