{"record":{"id":"f3a7b748fe26ff90","repo":"astrid-runtime/astrid","slug":"github-api-rate-limit-exceeded-try-again-later","errorCode":null,"errorMessage":"GitHub API rate limit exceeded - try again later","messagePattern":"GitHub API rate limit exceeded - try again later","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/astrid-cli/src/commands/capsule/install_update.rs","lineNumber":51,"sourceCode":"async fn fetch_github_latest_version(\n    client: &reqwest::Client,\n    org: &str,\n    repo: &str,\n) -> anyhow::Result<semver::Version> {\n    let api_url = format!(\"https://api.github.com/repos/{org}/{repo}/releases/latest\");\n    let response = client\n        .get(&api_url)\n        .send()\n        .await\n        .context(\"failed to reach GitHub API\")?;\n\n    if response.status() == reqwest::StatusCode::NOT_FOUND {\n        bail!(\"no GitHub releases found for {org}/{repo}\");\n    }\n    if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS\n        || response.status() == reqwest::StatusCode::FORBIDDEN\n    {\n        bail!(\"GitHub API rate limit exceeded - try again later\");\n    }\n    if !response.status().is_success() {\n        bail!(\"GitHub API returned {}\", response.status());\n    }\n\n    let json: serde_json::Value = response\n        .json()\n        .await\n        .context(\"failed to parse GitHub API response\")?;\n    let tag_name = json\n        .get(\"tag_name\")\n        .and_then(serde_json::Value::as_str)\n        .filter(|s| !s.is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"GitHub release has missing or empty tag_name\"))?;\n\n    let version_str = strip_version_prefix(tag_name);\n    semver::Version::parse(version_str)\n        .with_context(|| format!(\"GitHub tag '{tag_name}' is not valid semver\"))","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/capsule/install_update.rs#L33-L69","documentation":"Raised by `fetch_github_latest_version` when GitHub responds with 429 TOO_MANY_REQUESTS or 403 FORBIDDEN, which is how GitHub signals API rate limiting. Unauthenticated GitHub API calls are limited to 60 requests/hour per IP, and `astrid capsule update` can check many capsules in one run. The message explicitly tells you to retry later.","triggerScenarios":"Running `astrid capsule update` in workspace mode when the machine's IP has exhausted the unauthenticated GitHub API quota (60 req/hr), e.g. CI runners sharing egress IPs, or checking many GitHub-sourced capsules in one batch. Also 403 from GitHub's abuse/rate-limit responses.","commonSituations":"CI pipelines on shared runners; corporate NAT with many users hitting api.github.com; bulk updates of dozens of capsules in a loop; running update repeatedly in a short window.","solutions":["Wait until the rate-limit window resets (check the `X-RateLimit-Reset` response header) and re-run `astrid capsule update`.","Set GITHUB_TOKEN / provide authenticated GitHub credentials so the limit rises from 60 to 5,000 requests/hour, if your setup forwards them.","Update only the specific capsules you need (`astrid capsule update --workspace <name>`) instead of all capsules.","Check your network: switch off a shared VPN/runner IP or add caching in CI to avoid repeated update checks."],"exampleFix":"// before: unauthenticated, shared-IP CI run fails\nastrid capsule update --workspace\n// error: GitHub API rate limit exceeded - try again later\n\n// after: schedule less often and export a token\nexport GITHUB_TOKEN=ghp_...\nastrid capsule update --workspace","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match check_remote_version(&client, &source, &version) {\n    UpdateCheck::Failed { reason } if reason.contains(\"rate limit exceeded\") => {\n        tokio::time::sleep(backoff).await;\n        retry_with_backoff(3, backoff, || check_remote_version(&client, &source, &version)).await\n    },\n    other => handle(other),\n}","preventionTips":["Provide an authenticated GitHub token (GITHUB_TOKEN) to raise the 60/hr anonymous limit.","Cache the latest-version check result; do not re-check every invocation.","Batch update checks on a schedule (e.g. daily in CI) rather than per-command.","Target specific capsules for update instead of checking the entire fleet each time."],"tags":["network","github","rate-limit","http"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}