aaif-goose/goose · error

Failed to fetch repository contents using 'gh api' command (

Error message

Failed to fetch repository contents using 'gh api' command (executed when GOOSE_RECIPE_GITHUB_REPO is configured). This requires GitHub CLI (gh) to be installed and authenticated. Error: {}

What it means

discover_github_recipes lists available recipes by running `gh api repos/<repo>/contents` (crates/goose-cli/src/recipes/github_recipe.rs:250-263). This error is the spawn io failure of that command, mapped to a message explaining it requires the GitHub CLI installed and authenticated. It fires after ensure_gh_authenticated() succeeded, so in practice gh existed moments earlier.

Source

Thrown at crates/goose-cli/src/recipes/github_recipe.rs:263

/// Lists all available recipes from a GitHub repository
pub fn list_github_recipes(repo: &str) -> Result<Vec<RecipeInfo>> {
    discover_github_recipes(repo)
}

fn discover_github_recipes(repo: &str) -> Result<Vec<RecipeInfo>> {
    use serde_json::Value;
    use std::process::Command;

    // Ensure GitHub CLI is authenticated
    ensure_gh_authenticated()?;

    // Get repository contents using GitHub CLI
    let output = Command::new("gh")
        .args(["api", &format!("repos/{}/contents", repo)])
        .set_no_window()
        .output()
        .map_err(|e| anyhow!("Failed to fetch repository contents using 'gh api' command (executed when GOOSE_RECIPE_GITHUB_REPO is configured). This requires GitHub CLI (gh) to be installed and authenticated. Error: {}", e))?;

    if !output.status.success() {
        let error_msg = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow!("GitHub API request failed: {}", error_msg));
    }

    let contents: Value = serde_json::from_slice(&output.stdout)
        .map_err(|e| anyhow!("Failed to parse GitHub API response: {}", e))?;

    let mut recipes = Vec::new();

    if let Some(items) = contents.as_array() {
        for item in items {
            if let (Some(name), Some(item_type)) = (
                item.get("name").and_then(|n| n.as_str()),
                item.get("type").and_then(|t| t.as_str()),
            ) {
                if item_type == "dir" {

View on GitHub (pinned to 3810898a74)

Solutions

  1. Install/repair GitHub CLI and confirm `command -v gh && gh api repos/<owner>/<repo>/contents` works in goose's shell
  2. Check PATH stability of the environment (CI steps that strip PATH, direnv hooks) so gh stays resolvable for the whole goose invocation
  3. Re-run the command; a transient spawn failure (EAGAIN) clears when resource pressure drops
Defensive patterns

Strategy: validation

Validate before calling

# Exercise the exact API call goose will make
gh api "repos/$GOOSE_RECIPE_GITHUB_REPO/contents" >/dev/null && echo "gh api OK"

Prevention

When it happens

Trigger: Command::new("gh").output() io error: gh not on PATH (would normally be caught earlier by the auth check), binary replaced/removed between the auth check and this call, exec permission/format error, or resource exhaustion at spawn.

Common situations: Same class as [126] but on the discovery path (`goose recipe list` style flows with GOOSE_RECIPE_GITHUB_REPO set): missing gh on minimal systems, PATH drift in wrappers, or concurrent brew/winget upgrades swapping the gh binary mid-run.

Understand the failure class

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/0c528dc078eac4ff. Report an issue: GitHub.