chatboxai/chatbox · error · GitHubApiError

Failed to fetch file

Error message

Failed to fetch file: ${filePath}

What it means

Thrown by fetchFileContent() when raw.githubusercontent.com returns a non-OK status for a single file. Unlike githubFetch (which special-cases 404 and 403), this raw-URL path has no status-specific branches — any failure becomes a GitHubApiError with the original status code attached. The function powers SKILL.md reads during detection and individual file downloads.

Solutions

  1. Verify the file exists at the repo's default branch by opening the raw URL in a browser.
  2. If the repo is private, supply authentication — raw URLs need a token in the URL or use the contents API with an Authorization header.
  3. Retry once after a short delay for transient CDN 5xx; clearCache() is not needed since this URL was never cached on failure.
  4. For LFS files, fetch via the contents/blobs API instead of raw.

Example fix

// before
if (!response.ok) {
  throw new GitHubApiError(`Failed to fetch file: ${filePath}`, response.status)
}

// after — surface the status so callers can distinguish 404 (gone) from 5xx (retry)
if (!response.ok) {
  throw new GitHubApiError(
    `Failed to fetch file: ${filePath} (HTTP ${response.status} ${response.statusText})`,
    response.status
  )
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the file path shape before fetching.
function isValidFilePath(p: string): boolean {
  return typeof p === 'string' && p.length > 0 && p.length < 4096 && !p.includes('\\')
}
if (!isValidFilePath(filePath)) throw new Error('Invalid file path')

Type guard

function isGitHubApiError(e: unknown): e is { statusCode: number; message: string } {
  return e instanceof Error && typeof (e as any).statusCode === 'number'
}

Try / catch

try {
  return await fetchFileContent(owner, repo, filePath)
} catch (error) {
  if (isGitHubApiError(error) && (error.statusCode === 404)) return null // file gone
  if (isGitHubApiError(error) && error.statusCode >= 500) {
    return await backoffRetry(() => fetchFileContent(owner, repo, filePath))
  }
  throw error
}

Prevention

When it happens

Trigger: Requesting raw.githubusercontent.com/{owner}/{repo}/HEAD/{encodedPath} where the path does not exist at HEAD (404), the repo is private (404), the file is git-LFS tracked (pointer served, or 404 on media), or raw CDN returns 5xx. Also triggered when filePath contains characters that encodeURIComponent mishandles for the raw endpoint.

Common situations: A SKILL.md listed in the tree was deleted before the content fetch (HEAD advanced); private repo accessed without a token; LFS-backed file; transient raw CDN error. The per-segment encodeURIComponent handles # and ? in filenames, but a leading slash or unusual encoding can still produce a 404.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/d947627dd7cbe2a4. Report an issue: GitHub.

Appendix: source

Thrown at src/main/skills/github-fetcher.ts:324

  }

  return detected
}

export async function fetchFileContent(owner: string, repo: string, filePath: string): Promise<string> {
  // Git allows `#`/`?` in filenames — encode per segment so they don't truncate the URL
  const encodedPath = filePath.split('/').map(encodeURIComponent).join('/')
  const url = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${encodedPath}`

  const cached = getCached<string>(url)
  if (cached !== undefined) return cached

  const response = await fetch(url, {
    headers: { 'User-Agent': USER_AGENT },
  })

  if (!response.ok) {
    throw new GitHubApiError(`Failed to fetch file: ${filePath}`, response.status)
  }

  const content = await response.text()
  setCache(url, content)
  return content
}

export async function downloadSkillFiles(
  owner: string,
  repo: string,
  skillPath: string,
  targetDir: string
): Promise<void> {
  try {
    const downloaded = await downloadSkillFilesViaTree(owner, repo, skillPath, targetDir)
    if (downloaded) return
  } catch (error) {
    if (error instanceof GitHubApiError && (error.statusCode === 403 || error.statusCode === 429)) {

View on GitHub (pinned to 81571269ad)