davila7/claude-code-templates · error · anyhow::Error
unexpected contents API response
Error message
unexpected contents API response
What it means
The GitHub contents API response for a directory deserialized into JSON that is not an array. `walk` expects a list of entries when listing a directory path; receiving an object means the path points to a file, or GitHub returned an error object despite a 2xx-ish path, or the response shape changed.
Source
Thrown at cli-rust/src/github.rs:96
skill_base_name: &str,
out: &mut Vec<DownloadedFile>,
) -> Result<bool> {
let resp = client()?
.get(api_url)
.header("Accept", "application/vnd.github.v3+json")
.send()?;
if resp.status().as_u16() == 404 {
return Ok(false);
}
if !resp.status().is_success() {
return Err(anyhow!("HTTP {}", resp.status().as_u16()));
}
let contents: Value = resp.json()?;
let items = contents
.as_array()
.ok_or_else(|| anyhow!("unexpected contents API response"))?;
for item in items {
let name = item.get("name").and_then(|v| v.as_str()).unwrap_or("");
let item_type = item.get("type").and_then(|v| v.as_str()).unwrap_or("");
let item_path = if relative_path.is_empty() {
name.to_string()
} else {
format!("{relative_path}/{name}")
};
if item_type == "file" {
match item.get("download_url").and_then(|v| v.as_str()) {
Some(download_url) => match fetch_raw_optional(download_url) {
Some(content) => {
let executable = name.ends_with(".py") || name.ends_with(".sh");
out.push(DownloadedFile {
target_rel_path: format!(
".claude/skills/{skill_base_name}/{item_path}"View on GitHub (pinned to a0851ed10c)
Solutions
- Verify the path passed to walk is a directory in the repo, not a file
- If the directory is huge, use the Git Trees API (recursive) instead of the Contents API
- Add diagnostics: log the actual JSON value type/keys before failing
- Handle the 'truncated' object response by falling back to the trees endpoint
Example fix
// before
let items = contents.as_array().ok_or_else(|| anyhow!("unexpected contents API response"))?;
// after
let items = match contents {
Value::Array(items) => items,
Value::Object(o) if o.get("message").is_some() =>
return Err(anyhow!("contents API error: {}", o["message"])),
other => return Err(anyhow!("expected array, got {}: {other}",
match other { Value::Object(_) => "object", _ => "scalar" })),
}; Defensive patterns
Strategy: validation
Validate before calling
// Validate the path targets a directory before walking
// (contents API returns an object for files)
let meta = client()?.get(&format!(
"https://api.github.com/repos/{repo}/contents/{path}?ref={ref_}"))
.send()?.json::<Value>()?;
ensure!(meta.is_array(), "path {path} is not a directory"); Type guard
fn is_dir_listing(v: &Value) -> bool { v.is_array() } Try / catch
match contents.as_array() {
Some(items) => { /* walk items */ }
None => match &contents {
Value::Object(o) if o.get("message").is_some() =>
return Err(anyhow!("contents API: {}", o["message"])),
_ => return Err(anyhow!("expected directory listing, got non-array")),
},
} Prevention
- Prefer the Git Trees API (git/trees?recursive=1) for deep or large directories
- Assert directory semantics upstream (paths end in a dir, not a file)
- Log the raw JSON when shape validation fails so mismatches are diagnosable
When it happens
Trigger: walk() on a path that is a file (contents API returns a single object), a path that doesn't exist combined with a redirect, or truncated directory listings (contents API returns {"message": "This API returns blobs up to 1 MB..."} for huge dirs with array truncation metadata), or an unexpected API response shape from GitHub Enterprise.
Common situations: Pointing download_skill_tree at a file instead of a directory; skill directory containing >1000 entries; GitHub Enterprise Server with older API shapes; trailing-slash differences in path construction.
Related errors
- HTTP {}
- GitHub API error: ${response.status}
- Invalid workflow data structure in hash
- failed to launch Node CLI: {e}. Is Node.js installed?
- failed to build HTTP client: {e}
AI-assisted analysis of davila7/claude-code-templates@a0851ed10c (2026-08-28).
Data as JSON: /api/errors/865528d05e267061.
Report an issue: GitHub.