Hmbown/CodeWhale · error · Error
The update service is unavailable
Error message
The update service is unavailable (${response.status}). Try again later. What it means
checkForUpdate queries the GitHub releases/latest API with a 10s timeout; a 404 is treated as 'no release yet', but any other non-OK status throws this error embedding the HTTP status code. It signals the update service could not be reached successfully and the check should be retried later.
Solutions
- Wait and retry later (rate limits reset within an hour; transient outages resolve)
- Check the status in the message: 403 typically means GitHub API rate limiting — authenticate or wait
- Verify network/proxy access to api.github.com (curl -i https://api.github.com/rate_limit)
- Check the GitHub status page if 5xx statuses persist
Example fix
// before (unauthenticated, rate-limited on shared IPs)
fetch(url, { headers: { Accept: "application/vnd.github+json" } });
// after (supply a token to lift rate limits)
fetch(url, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } }); Defensive patterns
Strategy: retry
Validate before calling
const pre = await fetch("https://api.github.com/rate_limit", { signal: AbortSignal.timeout(5000) });
if (!pre.ok || pre.status !== 200) throw new Error("GitHub API unreachable or rate-limited before update check"); Try / catch
try {
await checkForUpdate();
} catch (e) {
if (e.message.includes("update service is unavailable")) {
await sleep(60_000); // back off, then retry
} else throw e;
} Prevention
- Authenticate GitHub API calls to avoid the 60/hr anonymous rate limit
- Add exponential backoff around checkForUpdate
- Monitor GitHub status before release windows
- Bypass or correctly configure proxies that tamper with api.github.com traffic
When it happens
Trigger: GitHub API returning 403 (rate limit), 5xx server errors, or other non-404 non-2xx statuses on GET /repos/.../releases/latest with the pinned Accept and API-version headers.
Common situations: Exhausting the unauthenticated GitHub API rate limit (60/hr) behind a shared IP; GitHub incident/outage; corporate proxy returning 401/407; a network appliance blocking api.github.com.
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
- building bundle fetch client failed
- bundle fetch failed with HTTP status
- bundle fetch request failed
- bundle redirects may not change URL scheme
- ${compactRuntimeError(response.status, body)}
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/2a97f844b7f051ed.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/app/updates.mjs:45
}
export function newerVersion(candidate,current) {
const parse=value=>/^\d+\.\d+\.\d+$/.test(value)?value.split(".").map(Number):null;
const a=parse(candidate),b=parse(current); if(!a||!b) return false;
for(let i=0;i<3;i++) { if(a[i]!==b[i]) return a[i]>b[i]; } return false;
}
export function releaseUpdate(release,current=APP_VERSION) {
const version=release?.tag_name?.replace(/^v/,"");
if(!version||release.draft||release.prerelease||!newerVersion(version,current)) return {available:false,message:`You have Computer Use ${current}. No newer stable installer is available.`};
const name=`Codewhale-Computer-Use-${version}-macos-universal.zip`;
const asset=release.assets?.find(asset=>asset.name===name);
const url=`${repository}/releases/download/v${version}/${name}`;
if(!asset||asset.browser_download_url!==url||!/^sha256:[a-f0-9]{64}$/.test(asset.digest)||!Number.isSafeInteger(asset.size)||asset.size<=0||asset.size>limit) return {available:false,message:`Version ${version} has no verified macOS installer yet.`};
return {available:true,version,url,sha256:asset.digest.slice(7),size:asset.size,message:`Computer Use ${version} is available. Install it to restart the helper; existing computer sessions will stop.`};
}
export async function checkForUpdate() {
const response=await fetch("https://api.github.com/repos/Hmbown/codewhale-cu-plugin/releases/latest",{redirect:"error",headers:{Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"},signal:AbortSignal.timeout(10_000)});
if(response.status===404) return {available:false,message:"No stable installer has been published yet. Your current app is unchanged."};
if(!response.ok) throw new Error(`The update service is unavailable (${response.status}). Try again later.`);
return releaseUpdate(JSON.parse((await responseBytes(response,1024*1024)).toString("utf8")));
}
/** Inspect both ZIP headers before extraction: no links, traversal or bombs. */
export function validateReleaseZip(bytes) {
const minimum=Math.max(0,bytes.length-65557); let end=-1;
for(let i=bytes.length-22;i>=minimum;i--) if(bytes.readUInt32LE(i)===0x06054b50&&i+22+bytes.readUInt16LE(i+20)===bytes.length) { end=i; break; }
if(end<0||bytes.readUInt16LE(end+4)||bytes.readUInt16LE(end+6)) throw new Error("Invalid update archive.");
const count=bytes.readUInt16LE(end+10); let position=bytes.readUInt32LE(end+16),total=0;
if(!count||count>2000||bytes.readUInt16LE(end+8)!==count||position+bytes.readUInt32LE(end+12)!==end) throw new Error("Invalid update archive index.");
const seen=new Set();
for(let i=0;i<count;i++) {
if(position+46>end||bytes.readUInt32LE(position)!==0x02014b50) throw new Error("Invalid update entry.");
const flags=bytes.readUInt16LE(position+8),method=bytes.readUInt16LE(position+10),length=bytes.readUInt16LE(position+28),extra=bytes.readUInt16LE(position+30),comment=bytes.readUInt16LE(position+32);
const name=bytes.subarray(position+46,position+46+length).toString("utf8");
const kind=(bytes.readUInt32LE(position+38)>>>16)&0xf000,offset=bytes.readUInt32LE(position+42),compressed=bytes.readUInt32LE(position+20);
const size=bytes.readUInt32LE(position+24); total+=size;
if(flags&1||![0,8].includes(method)||![0,0x4000,0x8000].includes(kind)||total>512*1024*1024||position+46+length+extra+comment>end) throw new Error("Unsupported update entry.");View on GitHub (pinned to 73e0f67d83)