Hmbown/CodeWhale · error · Error
The update could not be downloaded. Your current app is…
Error message
The update could not be downloaded. Your current app is unchanged.
What it means
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.
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.
Defensive patterns
Strategy: retry
Validate before calling
const head = await fetch(url, {method:'HEAD'}); if (!head.ok) throw new Error(`release asset unavailable: ${head.status}`); Try / catch
try { await prepareUpdate(update); } catch (e) { if (/could not be downloaded/.test(e.message)) { await backoffRetry(() => checkForUpdate().then(prepareUpdate), 3); } else throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- download failed with HTTP
- failed to download skill
- failed to download
- failed to fetch from
- failed to fetch from : HTTP
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a7cc74569d26dec9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/app/updates.mjs:96
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.");
const bundle=path.join(stage,`${APP_NAME}.app`); verifyReleaseBundle(bundle);
const version=spawnSync("/usr/libexec/PlistBuddy",["-c","Print :CFBundleShortVersionString",path.join(bundle,"Contents","Info.plist")],{encoding:"utf8"});
if(version.status!==0||version.stdout.trim()!==update.version) throw new Error("The downloaded app has a different version.");
return {stage,bundle};
} catch(error) { fs.rmSync(stage,{recursive:true,force:true}); throw error; }
}
export async function restartWithUpdate(prepared,destination) {
const logDir=path.join(os.homedir(),"Library","Logs",APP_NAME); fs.mkdirSync(logDir,{recursive:true});
const log=fs.openSync(path.join(logDir,"update.log"),"a",0o600);View on GitHub (pinned to 73e0f67d83)