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

  1. Verify the path passed to walk is a directory in the repo, not a file
  2. If the directory is huge, use the Git Trees API (recursive) instead of the Contents API
  3. Add diagnostics: log the actual JSON value type/keys before failing
  4. 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

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


AI-assisted analysis of davila7/claude-code-templates@a0851ed10c (2026-08-28). Data as JSON: /api/errors/865528d05e267061. Report an issue: GitHub.