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

  1. Re-run the check-for-update step so prepareUpdate() receives a fresh, app-generated descriptor instead of cached or hand-built metadata.
  2. 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.
  3. Verify update.version is strictly newer than the running APP_VERSION; if the app is already current, skip the update flow.
  4. 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

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


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)