{"record":{"id":"9c23c9288371cc07","repo":"Hmbown/CodeWhale","slug":"the-update-could-not-be-unpacked","errorCode":null,"errorMessage":"The update could not be unpacked.","messagePattern":"The update could not be unpacked\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":104,"sourceCode":"  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.\");\n  // Only GitHub's fixed release URL and its asset CDN can serve the bytes.\n  let url=update.url, response;\n  for(let redirects=0;redirects<4;redirects++) {\n    response=await fetch(url,{redirect:\"manual\",signal:AbortSignal.timeout(60_000)});\n    if(![301,302,303,307,308].includes(response.status)) break;\n    const next=new URL(response.headers.get(\"location\"),url);\n    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.\");\n    url=next.href;\n  }\n  if(!response?.ok) throw new Error(\"The update could not be downloaded. Your current app is unchanged.\");\n  const bytes=await responseBytes(response,update.size);\n  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.\");\n  validateReleaseZip(bytes);\n  const stage=fs.mkdtempSync(path.join(os.tmpdir(),\"codewhale-cu-release-\"));\n  try {\n    const archive=path.join(stage,\"release.zip\"); fs.writeFileSync(archive,bytes,{mode:0o600});\n    const result=spawnSync(\"ditto\",[\"-x\",\"-k\",archive,stage],{encoding:\"utf8\"});\n    if(result.status!==0) throw new Error(\"The update could not be unpacked.\");\n    const bundle=path.join(stage,`${APP_NAME}.app`); verifyReleaseBundle(bundle);\n    const version=spawnSync(\"/usr/libexec/PlistBuddy\",[\"-c\",\"Print :CFBundleShortVersionString\",path.join(bundle,\"Contents\",\"Info.plist\")],{encoding:\"utf8\"});\n    if(version.status!==0||version.stdout.trim()!==update.version) throw new Error(\"The downloaded app has a different version.\");\n    return {stage,bundle};\n  } catch(error) { fs.rmSync(stage,{recursive:true,force:true}); throw error; }\n}\n\nexport async function restartWithUpdate(prepared,destination) {\n  const logDir=path.join(os.homedir(),\"Library\",\"Logs\",APP_NAME); fs.mkdirSync(logDir,{recursive:true});\n  const log=fs.openSync(path.join(logDir,\"update.log\"),\"a\",0o600);\n  const child=spawn(process.execPath,[fileURLToPath(import.meta.url),\"--apply\",prepared.bundle,destination,String(process.pid),String(process.ppid)],{detached:true,stdio:[\"ignore\",log,log]});\n  try { await new Promise((resolve,reject)=>{child.once(\"spawn\",resolve);child.once(\"error\",reject);}); child.unref(); }\n  finally { fs.closeSync(log); }\n}\n\nif(process.argv[1]===fileURLToPath(import.meta.url)&&process.argv[2]===\"--apply\") {\n  const [source,destination,owner,launcher]=process.argv.slice(3);\n  let result;","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L86-L122","documentation":"prepareUpdate() extracts the downloaded release zip using macOS ditto (spawnSync(\"ditto\", [\"-x\",\"-k\",archive,stage])). A non-zero exit status from ditto means the archive could not be expanded into the staging directory; the stage is cleaned up and the error rethrown. This runs after checksum and zip-structure validation, so it usually indicates an extraction-level problem.","triggerScenarios":"ditto is missing or not executable on the host (non-standard macOS image); the staging path in os.tmpdir() is unwritable or full; the zip, though structurally valid, contains entries ditto refuses to extract (permission/resource-fork anomalies); disk space exhausted during extraction.","commonSituations":"Running under a stripped-down macOS environment or container where /usr/bin/ditto is unavailable; TMPDIR pointed at a read-only or tiny volume; a corrupted-adjacent zip that passed structural checks but trips ditto; security software blocking ditto execution.","solutions":["Verify /usr/bin/ditto exists and runs (ditto -h) on this Mac; it is present on standard macOS installs.","Check free disk space and that $TMPDIR (os.tmpdir()) is writable; clear space or fix TMPDIR if not.","Re-download the update to rule out an archive that passed sha256 but still trips extraction; if it persists, report the release asset as defective.","Check for security/EDR software blocking ditto execution and add an exception."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import {execFileSync} from 'child_process';\ntry { execFileSync('/usr/bin/ditto', ['--help'], {stdio:'ignore'}); } catch { throw new Error('ditto unavailable; updates unsupported on this host'); }\nimport statfs from 'node:fs'; // also verify tmpdir writability and free space before starting","typeGuard":null,"tryCatchPattern":"try { await prepareUpdate(update); } catch (e) { if (/could not be unpacked/.test(e.message)) { /* check ditto availability, TMPDIR, disk space; report defect if archive itself is bad */ } else throw e; }","preventionTips":["Only run the updater on standard macOS images where /usr/bin/ditto exists.","Keep adequate free disk space and a writable TMPDIR.","Don't run updates under stripped containers or EDR profiles that block ditto."],"tags":["macos","extraction","subprocess","update"],"backgroundTag":"subprocess-command-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}