aaif-goose/goose · error

subprocess exited with status

Error message

{label} subprocess exited with status {}: {}

What it means

goose review fans out its passes to child processes (`goose run -i -`, fed prompts over stdin). After wait_with_output(), a non-zero child exit status aborts the pass with this error, which embeds the exit status and up to 500 characters of the child's stderr (truncate).

Solutions

  1. Read the embedded stderr snippet — it identifies the root cause (auth error, 429, crash)
  2. Fix provider configuration via `goose configure` if the failure is auth-related
  3. Retry after the rate-limit window; consider a smaller model or reduced parallelism
  4. Narrow the review scope (`--range`, `--files`) to shorten worker lifetime
  5. Raise memory/turn budgets in CI if the child was killed
Defensive patterns

Strategy: retry

Try / catch

# Bash: inspect exit status + stderr tail, retry only transient causes
set +e
OUT=$(goose review --files 'src/**' 2>&1); RC=$?
set -e
if [ "$RC" -ne 0 ]; then
  printf '%s\n' "$OUT" | tail -n 5
  case "$OUT" in
    *429*|*rate*|*529*) sleep 60; exec goose review --files 'src/**' ;;
    *) exit "$RC" ;;
  esac
fi

Prevention

When it happens

Trigger: A review worker subprocess dying mid-run: provider auth failure (bad/expired key), rate limiting or 5xx from the model provider, or the child being OOM-killed in a constrained CI container.

Common situations: Large reviews hitting provider rate limits; credentials expiring between runs; memory-constrained CI killing workers; model outages surfacing as child failures.

Related errors


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

Appendix: source

Thrown at crates/goose-cli/src/commands/review/orchestrator.rs:281

        .with_context(|| format!("spawn subprocess for {label}"))?;

    if let Some(mut stdin) = child.stdin.take() {
        stdin
            .write_all(prompt.as_bytes())
            .await
            .with_context(|| format!("write prompt to {label} stdin"))?;
        // Closing stdin signals EOF to `goose run -i -`.
        drop(stdin);
    }

    let output = child
        .wait_with_output()
        .await
        .with_context(|| format!("wait on {label}"))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!(
            "{label} subprocess exited with status {}: {}",
            output.status,
            truncate(&stderr, 500)
        );
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    parse_findings(&stdout)
}

/// Run the main correctness pass as N parallel subprocesses, one per
/// touched file. This replaces the older in-process `session.headless()`
/// path which:
///
/// 1. Streamed text-mode chatter to stdout (not JSONL) so findings were
///    sometimes lost in interleaved output.
/// 2. Sent the entire diff in a single prompt — large diffs (1000+
///    lines) reliably caused Gemini 3.x to short-circuit with `[]`

View on GitHub (pinned to 3810898a74)