midudev/autoskills · error · Error
GitHub for
Error message
GitHub ${res.status} ${res.statusText} for ${url} What it means
ghFetch throws this generic error for any GitHub response that is not ok and is not the specific rate-limit case. The message embeds the HTTP status, status text, and the requested URL so the developer can see which GitHub endpoint failed and why (404 for missing repo/path, 401/403 for auth problems, 5xx for GitHub incidents).
Solutions
- Read the status and URL in the message: 404 means fix the repo/branch/path; 401 means fix the token; 5xx means retry later.
- Verify the repository, ref (from resolveRepoHead), and file paths in your sync config actually exist on GitHub.
- Check/regenerate GITHUB_TOKEN if the status is 401 or 403.
- For 5xx, wait and re-run the sync; check the GitHub status page for incidents.
Example fix
// before (config) const repo = "org/old-skill-repo"; // renamed -> 404 // GitHub 404 Not Found for https://api.github.com/repos/org/old-skill-repo/... // after const repo = "org/new-skill-repo";
Defensive patterns
Strategy: try-catch
Validate before calling
async function assertGithubUrlReachable(url, headers = {}) {
const res = await fetch(url, { method: "HEAD", headers });
if (!res.ok) throw new Error(`GitHub endpoint check failed: ${res.status} ${res.statusText} for ${url}`);
}
// validate repo/branch/path config before running the sync loop Try / catch
try {
const res = await ghFetch(url);
} catch (e) {
if (/GitHub \d{3}/.test(e.message) && !e.message.includes("rate limit")) {
const status = Number(e.message.match(/GitHub (\d{3})/)?.[1]);
if (status === 404) throw new Error(`Config error: repo/ref/path not found -> ${e.message}`);
if (status >= 500) return ghFetch(url); // transient: retry once
throw e;
} else throw e;
} Prevention
- Validate repo names, refs, and file paths in sync config against the GitHub API before bulk fetching.
- Handle 404 separately from 5xx: fix config vs. retry later.
- Log the offending URL (already in the message) with each sync run for quick diagnosis.
- Watch the GitHub status page and add retry/backoff around transient 5xx responses.
When it happens
Trigger: ghFetch receives a non-ok response other than 403-with-zero-remaining — e.g. requesting a repo tree or file that doesn't exist (404), an invalid token (401), or GitHub server errors (500/502).
Common situations: Typo in a repo name, branch, or skill path in the sync configuration; renamed/moved upstream repository; an expired PAT returning 401; transient GitHub incidents returning 5xx.
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
- Tarball fetch failed
- raw fetch failed
- GitHub rate limit exceeded
- download failed for
- GitHub 403 rate limit exceeded
AI-assisted analysis of midudev/autoskills@0ec725320d (2026-09-15).
Data as JSON: /api/errors/3dd9b633f9c8e646.
Report an issue: GitHub.
Appendix: source
Thrown at packages/autoskills/scripts/sync-skills.mjs:161
// ── GitHub helpers ───────────────────────────────────────────
async function ghFetch(url) {
const headers = {
"User-Agent": "autoskills-sync",
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
};
if (GITHUB_TOKEN) headers.Authorization = `Bearer ${GITHUB_TOKEN}`;
const res = await fetch(url, { headers });
if (!res.ok) {
const resetAt = Number(res.headers.get("x-ratelimit-reset") || 0) * 1000;
const resetSuffix = resetAt ? ` (resets ${new Date(resetAt).toISOString()})` : "";
if (res.status === 403 && res.headers.get("x-ratelimit-remaining") === "0") {
throw new Error(
`GitHub 403 rate limit exceeded${resetSuffix} for ${url}. Set GITHUB_TOKEN or GH_TOKEN to increase the limit.`,
);
}
throw new Error(`GitHub ${res.status} ${res.statusText} for ${url}`);
}
return res;
}
function resolveRepoHead(repo) {
const result = spawnSync(
"git",
["ls-remote", "--symref", `https://github.com/${repo}.git`, "HEAD"],
{
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
},
);
if (result.status !== 0) {
throw new Error(`git ls-remote failed for ${repo}: ${result.stderr.trim() || "unknown error"}`);
}
let defaultBranch = "main";View on GitHub (pinned to 0ec725320d)