aaif-goose/goose · error
Failed to parse GitHub API response: {}
Error message
Failed to parse GitHub API response: {} What it means
goose parses the stdout of `gh api repos/<repo>/contents` as JSON (crates/goose-cli/src/recipes/github_recipe.rs:270-272). serde_json::from_slice failed, meaning gh exited zero but its stdout was not a valid JSON document — gh debug output, warnings, or a proxy-injected page got mixed into stdout instead of the expected API payload.
Source
Thrown at crates/goose-cli/src/recipes/github_recipe.rs:271
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
- Run `gh api repos/<owner>/<repo>/contents | head` and inspect what precedes/breaks the JSON; unset GH_DEBUG and GH_DEBUG_API for the goose invocation
- Upgrade or pin a known-good gh version if its stdout format changed
- Remove any proxy/wrapper that decorates stdout for gh commands
Example fix
# before export GH_DEBUG=api export GOOSE_RECIPE_GITHUB_REPO=owner/repo goose recipe list # after unset GH_DEBUG GH_DEBUG_API export GOOSE_RECIPE_GITHUB_REPO=owner/repo goose recipe list
Defensive patterns
Strategy: validation
Validate before calling
# Confirm gh emits pure JSON in this environment
gh api "repos/$GOOSE_RECIPE_GITHUB_REPO/contents" | python3 -c 'import json,sys; json.load(sys.stdin); print("valid JSON")' Try / catch
// Rust: on parse failure, include a snippet of the offending stdout
let contents: Value = match serde_json::from_slice(&output.stdout) {
Ok(v) => v,
Err(e) => anyhow::bail!(
"non-JSON gh output ({e}): {}",
String::from_utf8_lossy(&output.stdout[..output.stdout.len().min(200)])
),
}; Prevention
- Unset GH_DEBUG/GH_DEBUG_API when invoking tools that parse gh stdout
- Pin a known-good gh version in CI images
- Avoid proxies/wrappers that decorate stdout of child processes
When it happens
Trigger: GH_DEBUG/GH_DEBUG_API set makes gh print request logs to stdout alongside the body; a wrapper/proxy rewrites the response; gh prints a banner or non-JSON notice (update prompts) to stdout; or truncated output from a killed process.
Common situations: Debug env vars left exported in the shell, gh version changes that emit notices, terminals/pagers interfering when output is captured, or HTTPS proxies returning HTML error pages that gh passes through.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Failed to parse OpenAI evaluation response after {max_retrie
- Failed to parse tie-breaker response after {max_retries} att
- GitHub API request failed: {}
- Failed to access directory: {}
- Failed to parse directory contents: {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/30da4c2f37f798eb.
Report an issue: GitHub.