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

  1. Read the status and URL in the message: 404 means fix the repo/branch/path; 401 means fix the token; 5xx means retry later.
  2. Verify the repository, ref (from resolveRepoHead), and file paths in your sync config actually exist on GitHub.
  3. Check/regenerate GITHUB_TOKEN if the status is 401 or 403.
  4. 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

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


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)