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

  1. Wait and retry later (rate limits reset within an hour; transient outages resolve)
  2. Check the status in the message: 403 typically means GitHub API rate limiting — authenticate or wait
  3. Verify network/proxy access to api.github.com (curl -i https://api.github.com/rate_limit)
  4. 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

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


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)