gitbutlerapp/gitbutler · error

Failed to update pull request

Error message

Failed to update pull request: {}

What it means

The GitHub REST PATCH call used to update a pull request (title, body, state, etc.) returned a non-success HTTP status. The library bails with the response body (rendered via response_error) so you can see GitHub's actual rejection reason. It signals the update request itself failed server-side, not a local validation problem.

Solutions

  1. Read the appended response_error output to see GitHub's concrete reason and fix the request field it names
  2. Verify the token has repo/pull-requests write scope (classic) or Pull requests: write (fine-grained)
  3. Confirm the PR number exists in owner/repo (e.g. via gh pr view or get_pull_request)
  4. Check for 403/429 rate-limit or SSO issues and retry after the rate-limit reset

Example fix

// before
client.update_pull_request(&UpdatePullRequestParams { owner, repo, pr_number: 0, .. }).await?;
// after
if pr_number > 0 {
    client.update_pull_request(&UpdatePullRequestParams { owner, repo, pr_number, .. }).await?;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling: ensure the PR exists and the token can write
let pr = client.get_pull_request(owner, repo, pr_number).await?;
assert!(pr_number > 0);

Try / catch

// Rust: match on the error and inspect the embedded GitHub response body
match client.update_pull_request(&params).await {
    Ok(pr) => pr,
    Err(e) if e.to_string().contains("Failed to update pull request") => {
        eprintln!("GitHub rejected the update: {e}");
        // 403 -> check token scopes; 404 -> check owner/repo/number
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling update_pull_request with parameters GitHub rejects: nonexistent PR number, wrong owner/repo, token lacking write access to the repo or PR, closed PRs receiving state changes not allowed, invalid field values, or network/429/5xx responses.

Common situations: Using a fine-grained PAT without pull request write permission; stale PR numbers after rebase/force-push cleanup; typos in owner/repo; hitting rate limits during automation loops; trying to update a PR in an archived repository.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/8e0bf28dd505be2c. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-github/src/client.rs:1442

            state: Option<&'a str>,
        }

        let url = format!(
            "{}/repos/{}/{}/pulls/{}",
            self.base_url, params.owner, params.repo, params.pr_number
        );

        let body = UpdatePullRequestBody {
            title: params.title,
            body: params.body,
            base: params.base,
            state: params.state,
        };

        let response = self.client.patch(&url).json(&body).send().await?;

        if !response.status().is_success() {
            bail!(
                "Failed to update pull request: {}",
                response_error(response).await
            );
        }

        let pr: GitHubPullRequest = response.json().await?;
        Ok(pr.into())
    }

    /// Merge a PR.
    pub async fn merge_pull_request(&self, params: &MergePullRequestParams<'_>) -> Result<()> {
        #[derive(Serialize)]
        struct MergePullRequestBody<'a> {
            #[serde(skip_serializing_if = "Option::is_none")]
            commit_title: Option<&'a str>,
            #[serde(skip_serializing_if = "Option::is_none")]
            commit_message: Option<&'a str>,
            #[serde(skip_serializing_if = "Option::is_none")]

View on GitHub (pinned to 58e5313667)