aaif-goose/goose · error

GitHub API request failed: {}

Error message

GitHub API request failed: {}

What it means

The `gh api repos/<repo>/contents` call executed but exited non-zero; goose surfaces gh's stderr verbatim after this prefix (crates/goose-cli/src/recipes/github_recipe.rs:265-268). The stderr text is the authoritative cause — typically HTTP 404 (no such repo), 403 (rate limit or missing scope), or 401 (bad token).

Source

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

}

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" {
                    // Check if this directory contains a recipe file
                    if let Ok(recipe_info) = check_github_directory_for_recipe(repo, name) {
                        recipes.push(recipe_info);
                    }

View on GitHub (pinned to 3810898a74)

Solutions

  1. Run the same call manually to see the exact status: `gh api repos/<owner>/<repo>/contents` — 404 means wrong name/no access, 403 usually rate limit
  2. Fix GOOSE_RECIPE_GITHUB_REPO to the correct owner/repo and confirm access with `gh repo view`
  3. If rate-limited, wait for the reset window (check `gh api rate_limit`) or authenticate with a token that raises the limit
Defensive patterns

Strategy: try-catch

Validate before calling

# Check the endpoint and rate limit headroom first
gh api "repos/$GOOSE_RECIPE_GITHUB_REPO/contents" --jq 'length'
gh api rate_limit --jq '.resources.core'

Try / catch

// Rust: always surface gh's stderr body with the exit status
if !output.status.success() {
    anyhow::bail!(
        "GitHub API request failed ({}): {}",
        output.status,
        String::from_utf8_lossy(&output.stderr)
    );
}

Prevention

When it happens

Trigger: GOOSE_RECIPE_GITHUB_REPO names a nonexistent repo; the authenticated token lacks access to a private repo; GitHub API rate limit exceeded; network/proxy returning an error gh reports as failure.

Common situations: Typo in the env var value, private org repo where the token needs SSO authorization, heavy scripting hitting the secondary rate limit, or corporate proxies intercepting api.github.com.

Related errors


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