Hmbown/CodeWhale · error · Error
The update identity is invalid.
Error message
The update identity is invalid.
What it means
prepareUpdate() in the computer-use plugin validates the update descriptor passed to it before downloading anything. The descriptor must have available=true, a version strictly newer than the running APP_VERSION, a url exactly matching the fixed GitHub release asset URL for that version, and a size that is a safe positive integer within the download limit. Any deviation means the descriptor was not produced by this app's own release check, so it throws rather than downloading untrusted bytes.
Solutions
- Re-run the check-for-update step so prepareUpdate() receives a fresh, app-generated descriptor instead of cached or hand-built metadata.
- Confirm update.url matches the exact expected GitHub release asset URL pattern for update.version, including the repository base and the -macos-universal.zip suffix.
- Verify update.version is strictly newer than the running APP_VERSION; if the app is already current, skip the update flow.
- Ensure update.size is present and a positive safe integer within the configured download limit (re-derive it from the release asset metadata).
Example fix
// before
await prepareUpdate({available:true, version:"1.2.3", url:"https://mirror.example.com/app.zip", size:null});
// after
await prepareUpdate({available:true, version:"1.4.0", url:`${repository}/releases/download/v1.4.0/Codewhale-Computer-Use-1.4.0-macos-universal.zip`, size:48213376, sha256:"<hex>"}); Defensive patterns
Strategy: validation
Validate before calling
const expectedUrl = `${repository}/releases/download/v${u.version}/Codewhale-Computer-Use-${u.version}-macos-universal.zip`;
if (!u?.available || !newerVersion(u.version, APP_VERSION) || u.url !== expectedUrl || !Number.isSafeInteger(u.size) || u.size <= 0 || u.size > limit) skipUpdate(); Type guard
function isValidUpdate(u) { return !!u && u.available === true && typeof u.version === 'string' && typeof u.url === 'string' && Number.isSafeInteger(u.size) && u.size > 0 && typeof u.sha256 === 'string'; } Try / catch
try { await prepareUpdate(update); } catch (e) { if (e.message === 'The update identity is invalid.') { /* re-run check-for-update or skip */ } else throw e; } Prevention
- Always feed prepareUpdate the descriptor produced by the app's own update check, never cached or hand-built metadata.
- Validate the descriptor shape (version/url/size/sha256) as soon as it is received.
- Drop the descriptor once the app version reaches or exceeds it.
When it happens
Trigger: Calling prepareUpdate() with an update object where update.available is truthy but: update.version is not newer than APP_VERSION (or equal/older), update.url differs from `${repository}/releases/download/v${version}/Codewhale-Computer-Use-${version}-macos-universal.zip`, or update.size is missing, negative, zero, non-integer, non-safe-integer, or above the size limit.
Common situations: Hand-constructing or persisting a stale update descriptor (e.g. cached metadata from an older release whose asset URL pattern changed); a MITM'd or tampered metadata source; calling prepareUpdate after the app already updated to the version in the descriptor; a typo'd or proxy-rewritten download URL in custom update metadata.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- append_allow_rules only accepts action = "allow"
- Cargo metadata dependencies for
- Codewhale credentials directory has an unsupported component
- Codewhale terminal receipt exceeded its string bound
- CodewhalePet/1
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f03eb44d3119ac79.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/app/updates.mjs:86
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;
}
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.");View on GitHub (pinned to 73e0f67d83)