Hmbown/CodeWhale · error · anyhow::Error

gh api POST repos/ /pulls/ /reviews failed

Error message

gh api POST repos/{repo}/pulls/{number}/reviews failed: {stderr}

What it means

The code shells out to `gh api POST repos/{repo}/pulls/{number}/reviews` to publish a PR review. When the gh subprocess exits non-zero, its stderr is surfaced verbatim in this error. It means GitHub rejected the review-creation request.

Solutions

  1. Run `gh auth status` and re-authenticate with `gh auth login` / `gh auth refresh -s repo`
  2. Verify the PR exists and is reachable: `gh api repos/{repo}/pulls/{number}` with the same repo/number values
  3. Re-read the stderr embedded in the error — it contains GitHub's specific reason (401/403/404/422) and address that directly

Example fix

// before
$ gh auth status  # token expired
// after
$ gh auth login && gh auth status  # logged in, retry publish
Defensive patterns

Strategy: try-catch

Validate before calling

if std::process::Command::new("gh").arg("auth").arg("status").status().map(|s|!s.success()).unwrap_or(true) { /* prompt re-auth before publishing */ }

Try / catch

match publish_result { Err(e) if e.to_string().contains("gh api POST") => { // inspect embedded stderr; check gh auth status, PR number, repo access before retry } , Ok(_) => {} }

Prevention

When it happens

Trigger: `gh api` call to create a PR review fails: expired/missing gh auth token, wrong PR number, repo renamed or inaccessible, network failure, or review body rejected by GitHub (e.g. invalid markdown payload size).

Common situations: gh session expired (`gh auth token` stale after SSO logout); publishing to a private repo the token cannot read; a fork where the PR number belongs to the upstream repo; corporate proxy blocking api.github.com.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/eec84992d0decf4b. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lib.rs:9741

        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped());
    let mut child = cmd
        .spawn()
        .map_err(|e| anyhow::anyhow!("Failed to run `gh api`: {e}"))?;
    *publication = ReviewPublication::Uncertain;
    if let Some(stdin) = child.stdin.as_mut() {
        use std::io::Write;
        stdin
            .write_all(serde_json::to_string(&payload)?.as_bytes())
            .map_err(|e| anyhow::anyhow!("Failed to write review payload: {e}"))?;
    }
    let output = child
        .wait_with_output()
        .map_err(|e| anyhow::anyhow!("Failed to wait for `gh api`: {e}"))?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        bail!("gh api POST repos/{repo}/pulls/{number}/reviews failed: {stderr}");
    }
    *publication = ReviewPublication::Posted;
    Ok(())
}

/// Publish a completed PR review exactly once: resolve the repository, render
/// the summary, and include every comment whose position the diff confirms.
/// A failed request can have an uncertain remote outcome, so reconciliation
/// and any retry stay under the caller's control rather than risking a duplicate.
fn post_pr_review(
    number: u32,
    view: &GhPullRequest,
    repo: Option<&str>,
    review: &crate::tools::review::ReviewOutput,
    diff: &str,
    publication: &mut ReviewPublication,
) -> Result<()> {
    crate::tools::review_pr::ensure_current(number, repo, &std::env::current_dir()?, view)?;

View on GitHub (pinned to 73e0f67d83)