{"record":{"id":"5ef878fd7693d899","repo":"coleam00/Archon","slug":"expected-directory-listing-from-url-got-a-sing","errorCode":null,"errorMessage":"Expected directory listing from ${url}, got a single file","messagePattern":"Expected directory listing from (.+?), got a single file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/workflow.ts","lineNumber":5110,"sourceCode":"  owner: string,\n  repo: string,\n  path: string,\n  sha: string\n): Promise<GitHubContentItem[]> {\n  const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${sha}`;\n  let res: Response;\n  try {\n    res = await fetch(url, { headers: { Accept: 'application/vnd.github.v3+json' } });\n  } catch (error) {\n    const err = error as Error;\n    throw new Error(`Cannot reach GitHub API: ${err.message}`);\n  }\n  if (!res.ok) {\n    throw new Error(`GitHub API error: HTTP ${String(res.status)} from ${url}`);\n  }\n  const data: unknown = await res.json();\n  if (!Array.isArray(data)) {\n    throw new Error(`Expected directory listing from ${url}, got a single file`);\n  }\n  return data as GitHubContentItem[];\n}\n\n/** Download a file from raw.githubusercontent.com at a pinned SHA. */\nasync function downloadRawFile(\n  owner: string,\n  repo: string,\n  filePath: string,\n  sha: string\n): Promise<string> {\n  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${sha}/${filePath}`;\n  let res: Response;\n  try {\n    res = await fetch(rawUrl);\n  } catch (error) {\n    const err = error as Error;\n    throw new Error(`Cannot fetch ${rawUrl}: ${err.message}`);","sourceCodeStart":5092,"sourceCodeEnd":5128,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/cli/src/commands/workflow.ts#L5092-L5128","documentation":"Thrown when the GitHub contents API returns a JSON object (a single file record) instead of the expected array of directory entries. The marketplace listing code calls the contents API expecting a directory; a non-array response means the URL pointed at a file, not a directory. It guards `fetchDirectoryListing`'s contract that callers receive `GitHubContentItem[]`.","triggerScenarios":"Calling `fetchDirectoryListing` (packages/cli/src/commands/workflow.ts:5110 area) with a GitHub contents API URL whose `path` resolves to a single file, or an API endpoint that returns an object payload rather than an array.","commonSituations":"A marketplace `sourceUrl` or directory path in a workflow entry points at the YAML file itself instead of its containing directory; GitHub returns `{type:'file',...}` for file paths; repo layout changed so the assumed directory is now a file; a truncated/symlinked path resolves to a blob.","solutions":["Fix the directory path so it points at a folder containing the workflow YAML files, not at a file","Verify with `gh api repos/<owner>/<repo>/contents/<path>` that the endpoint returns an array","Update the marketplace entry's sourceUrl for that workflow","Handle the single-file shape explicitly if a file URL is legitimate input"],"exampleFix":"// before\nthrow new Error(`Expected directory listing from ${url}, got a single file`);\n// after\nif (!Array.isArray(data)) {\n  // treat a single-file response as a one-item listing instead of failing\n  return [data as GitHubContentItem];\n}","handlingStrategy":"type-guard","validationCode":"const res = await fetch(url);\nconst data: unknown = await res.json();\nif (!Array.isArray(data)) {\n  throw new Error(`${url} is not a directory (got ${typeof data === 'object' && data ? (data as {type?:string}).type : typeof data})`);\n}","typeGuard":"function isDirectoryListing(data: unknown): data is GitHubContentItem[] {\n  return Array.isArray(data) && data.every(\n    (i): i is GitHubContentItem => typeof i === 'object' && i !== null && 'name' in i && 'type' in i\n  );\n}","tryCatchPattern":"try {\n  const items = await fetchDirectoryListing(url);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('got a single file')) {\n    // fall back to single-file handling path\n  } else throw e;\n}","preventionTips":["Use directory URLs (no file path suffix) when calling the directory-listing helper","Probe with `gh api repos/<owner>/<repo>/contents/<path>` before wiring a new marketplace entry","Keep marketplace sourceUrl paths pointing at folders","Handle both file and directory shapes at the caller if inputs are mixed"],"tags":["github-api","marketplace","http","unexpected-response-shape"],"backgroundTag":"github-api-unexpected-response-shape","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}