midudev/autoskills · error · Error

git tree truncated for

Error message

git tree truncated for ${repo}@${sha.slice(0, 7)}

What it means

fetchRepoTree calls the GitHub Git Trees API with ?recursive=1 to list every file in a commit. GitHub truncates trees exceeding size/entry limits and sets body.truncated=true; the script throws rather than syncing from an incomplete file list, which would silently miss skills.

Solutions

  1. Sync from a repo with a smaller tree (split skills into a dedicated repo)
  2. Use a narrower ref/SHA whose tree is under the limit
  3. Add a fallback path using `git clone --depth 1 --filter=blob:none` or sparse checkout instead of the Trees API
  4. Check whether GitHub's tree limits have changed and whether an API upgrade offers pagination

Example fix

// before
const tree = await fetchRepoTree("org/monorepo", sha); // throws: truncated
// after
const tree = await fetchRepoTree("org/skills", sha); // dedicated small repo
Defensive patterns

Strategy: fallback

Validate before calling

// detect a likely-truncated tree before syncing huge repos
const res = await fetch(`https://api.github.com/repos/${repo}`, { headers: { Authorization: `Bearer ${GITHUB_TOKEN}` } });
const info = await res.json();
if (info.size > 500000) console.warn(`${repo} is large (${info.size}KB); tree API may truncate`);

Try / catch

let tree;
try {
  tree = await fetchRepoTree(repo, sha);
} catch (err) {
  if (err.message.startsWith("git tree truncated")) {
    // fallback: sparse clone instead of the Trees API
    tree = await materializeSkillsFromSparseClone(repo, branch, skillNames, destRoot);
  } else throw err;
}

Prevention

When it happens

Trigger: GET /repos/<repo>/git/trees/<sha>?recursive=1 returns a JSON body with truncated:true — typically repos with tens of thousands of files or very large trees.

Common situations: Syncing skills from a huge monorepo; a repo with vendored dependencies inflating the tree; the truncated flag appearing only on large repos so it surfaces after the script works on smaller ones.

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/0c548c0419694002. Report an issue: GitHub.

Appendix: source

Thrown at packages/autoskills/scripts/sync-skills.mjs:232

  if (GITHUB_TOKEN) headers.Authorization = `Bearer ${GITHUB_TOKEN}`;
  const ac = new AbortController();
  const timer = setTimeout(() => ac.abort(), TARBALL_TIMEOUT_MS);
  try {
    const res = await fetch(url, { headers, signal: ac.signal });
    if (!res.ok || !res.body) {
      throw new Error(`Tarball fetch failed: ${res.status} ${url}`);
    }
    await pipeline(res.body, createWriteStream(destFile));
  } finally {
    clearTimeout(timer);
  }
}

async function fetchRepoTree(repo, sha) {
  const res = await ghFetch(`https://api.github.com/repos/${repo}/git/trees/${sha}?recursive=1`);
  const body = await res.json();
  if (body.truncated) {
    throw new Error(`git tree truncated for ${repo}@${sha.slice(0, 7)}`);
  }
  return body.tree || [];
}

function findSkillDirsInTree(tree, skillName) {
  // Returns array of { dir } where dir/SKILL.md exists in the tree.
  const skillPaths = tree
    .filter((t) => t.type === "blob" && /(^|\/)SKILL\.md$/i.test(t.path))
    .map((t) => t.path);

  const candidates = [];
  for (const p of skillPaths) {
    const parts = p.split("/");
    parts.pop();
    const parent = parts[parts.length - 1];
    if (parent === skillName) {
      candidates.push(parts.join("/"));
      continue;

View on GitHub (pinned to 0ec725320d)