{"record":{"id":"a7cc74569d26dec9","repo":"Hmbown/CodeWhale","slug":"the-update-could-not-be-downloaded-your-current-app-is","errorCode":null,"errorMessage":"The update could not be downloaded. Your current app is unchanged.","messagePattern":"The update could not be downloaded\\. Your current app is unchanged\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/updates.mjs","lineNumber":96,"sourceCode":"    position+=46+length+extra+comment;\n  }\n  if(position!==end) throw new Error(\"Invalid update archive length.\");\n  return count;\n}\n\nexport async function prepareUpdate(update) {\n  if(!update?.available) throw new Error(\"Check for an available update first.\");\n  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);","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/updates.mjs#L78-L114","documentation":"After following redirects, prepareUpdate() checks response.ok on the final fetch. If the final response is not OK (4xx/5xx, network-level failure surfaced as a falsy response, or the redirect loop exhausted without a successful response), it throws this error and leaves the currently installed app untouched. This is a deliberate no-partial-update guarantee.","triggerScenarios":"The release asset URL returns 404 (release removed or asset renamed), 403 (rate-limited by GitHub or blocked by a proxy), 5xx from GitHub's CDN, or the 4-redirect budget is exhausted while still receiving redirect statuses; AbortSignal.timeout(60s) expiring on a slow link also surfaces here or as a fetch rejection.","commonSituations":"GitHub is down or degraded; a corporate firewall blocks objects.githubusercontent.com; the release was deleted or re-tagged so the fixed asset URL no longer resolves; offline or captive-portal networks; GitHub API/CDN rate limiting.","solutions":["Check network connectivity and that the release asset URL opens in a browser (curl -I <url> shows 200).","Verify the release still exists and the asset was not renamed or removed; if re-released, re-run the update check to get a fresh descriptor.","Wait and retry if GitHub status is degraded or you are rate-limited (403 with x-ratelimit headers).","Disable or bypass firewalls/proxies blocking objects.githubusercontent.com and release-assets.githubusercontent.com.","Re-run the app's check-for-update step after conditions improve; the current installation is intentionally unchanged."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const head = await fetch(url, {method:'HEAD'}); if (!head.ok) throw new Error(`release asset unavailable: ${head.status}`);","typeGuard":null,"tryCatchPattern":"try { await prepareUpdate(update); } catch (e) { if (/could not be downloaded/.test(e.message)) { await backoffRetry(() => checkForUpdate().then(prepareUpdate), 3); } else throw e; }","preventionTips":["Check network/GitHub status before initiating updates.","Ensure firewalls allow github.com, release-assets.githubusercontent.com, and objects.githubusercontent.com.","Re-check for updates after release maintenance to get a valid asset URL."],"tags":["network","http","download","update"],"backgroundTag":"http-error-response","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"}