{"record":{"id":"bdc1372e82933ea6","repo":"chatboxai/chatbox","slug":"found-limitedpaths-length-skill-s-in-owner","errorCode":null,"errorMessage":"Found ${limitedPaths.length} skill(s) in ${owner}/${repo} but failed to fetch their contents","messagePattern":"Found (.+?) skill\\(s\\) in (.+?)/(.+?) but failed to fetch their contents","errorType":"exception","errorClass":"SkillContentUnavailableError","httpStatus":null,"severity":"error","filePath":"src/main/skills/github-fetcher.ts","lineNumber":201,"sourceCode":"        try {\n          const content = await fetchFileContent(owner, repo, skillMdPath)\n          return {\n            name: extractSkillName(content) || skillPath.split('/').pop() || repo,\n            path: skillPath,\n            description: extractSkillDescription(content),\n          }\n        } catch (error) {\n          log.warn(`Failed to fetch ${skillMdPath} from ${owner}/${repo}`, error)\n          fetchFailures++\n          return null\n        }\n      })\n    )\n    detected.push(...batch.filter((skill): skill is DetectedSkill => skill !== null))\n  }\n\n  if (limitedPaths.length > 0 && fetchFailures === limitedPaths.length) {\n    throw new SkillContentUnavailableError(\n      `Found ${limitedPaths.length} skill(s) in ${owner}/${repo} but failed to fetch their contents`\n    )\n  }\n  return detected\n}\n\n// Strategy 1: root SKILL.md | 2: skills/{name}/SKILL.md | 3: {dir}/skills/{name}/SKILL.md (fallback)\nasync function detectSkillsViaContents(owner: string, repo: string): Promise<DetectedSkill[]> {\n  const detected: DetectedSkill[] = []\n\n  try {\n    const rootContents = await fetchRepoContents(owner, repo)\n    const rootSkillMd = rootContents.find((item) => item.name === 'SKILL.md' && item.type === 'file')\n    if (rootSkillMd) {\n      const content = await fetchFileContent(owner, repo, 'SKILL.md')\n      const name = extractSkillName(content)\n      detected.push({\n        name: name || repo,","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/github-fetcher.ts#L183-L219","documentation":"Thrown by detectSkillsViaTree() when the repo tree listing found one or more SKILL.md paths but EVERY one of them failed to download. Partial failures (some skills fetched) are tolerated and the successfully-detected skills are returned; only total failure raises this SkillContentUnavailableError. It signals that the repo structure is discoverable but its contents are unreachable, which usually points to transient raw.githubusercontent failures or a mismatch between the tree SHA and HEAD.","triggerScenarios":"detectSkillsInRepo runs the tree strategy, the /git/trees/HEAD call succeeds and returns SKILL.md paths, but every raw.githubusercontent.com/{owner}/{repo}/HEAD/{path} fetch in fetchFileContent returns non-OK. Happens when HEAD moved between the tree fetch and the content fetch (race), when raw CDN is flaky, or when all SKILL.md files live behind LFS or are git-ignored at the raw endpoint.","commonSituations":"Repo uses Git LFS for SKILL.md (raw URL serves an LFS pointer, not 200); a brief raw.githubusercontent.com outage coincides with the batched fetch window; the repo was force-pushed between the tree and content calls so HEAD no longer matches the cached tree SHA.","solutions":["Retry the scan — clearCache() first to drop any stale tree entry, then re-run skills:scan-repo.","If the repo uses Git LFS, fetch SKILL.md via the contents API or the LFS media endpoint instead of raw.githubusercontent.com.","Confirm raw.githubusercontent.com is reachable from the user's network (some corporate proxies block it).","Fall back to the contents-API strategy (detectSkillsViaContents), which the caller already does when this strategy returns null."],"exampleFix":"// before\nif (limitedPaths.length > 0 && fetchFailures === limitedPaths.length) {\n  throw new SkillContentUnavailableError(\n    `Found ${limitedPaths.length} skill(s) in ${owner}/${repo} but failed to fetch their contents`\n  )\n}\n\n// after — treat total failure as null so the caller falls through to the contents strategy\nif (limitedPaths.length > 0 && fetchFailures === limitedPaths.length) {\n  log.warn(`All ${limitedPaths.length} skill fetches failed for ${owner}/${repo}; returning null for fallback`)\n  return null\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"function isSkillContentUnavailableError(e: unknown): boolean {\n  return e instanceof Error && /failed to fetch their contents/i.test(e.message)\n}","tryCatchPattern":"let skills: DetectedSkill[]\ntry {\n  skills = await scanRepoViaTree(owner, repo)\n} catch (error) {\n  if (isSkillContentUnavailableError(error)) {\n    // Tree strategy found paths but all contents failed — fall back to contents API.\n    skills = await detectSkillsViaContents(owner, repo)\n  } else {\n    throw error\n  }\n}","preventionTips":["Treat the tree strategy as best-effort and always wire the contents-API fallback.","Retry raw file fetches with short backoff before declaring total failure.","Watch for Git LFS in target repos — raw URLs do not serve LFS content."],"tags":["github-api","skills","network","fallback","content-fetch"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}