gitbutlerapp/gitbutler · error · anyhow::Error

Failed to update pull request: {status} - {error_text}

Error message

Failed to update pull request: {status} - {error_text}

What it means

Raised by update_pull_request's underlying PUT to /repositories/{workspace}/{repo_slug}/pullrequests/{id} when Bitbucket answers non-2xx; the message carries both the status code and the raw response body (error_text), which usually contains Bitbucket's JSON error detail naming the invalid field or unmet precondition.

Source

Thrown at crates/but-bitbucket/src/client.rs:380

    async fn send_pr_update(
        &self,
        workspace: &str,
        repo_slug: &str,
        id: i64,
        body: &UpdatePullRequestBody<'_>,
    ) -> Result<BitbucketPullRequest> {
        let url = format!(
            "{}/repositories/{}/{}/pullrequests/{}",
            self.base_url,
            urlencoding::encode(workspace),
            urlencoding::encode(repo_slug),
            id,
        );
        let response = self.client.put(&url).json(body).send().await?;
        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            bail!("Failed to update pull request: {status} - {error_text}");
        }
        let pr: BitbucketApiPullRequest = response.json().await?;
        Ok(pr.into())
    }

    pub async fn update_pull_request(
        &self,
        params: &UpdatePullRequestParams<'_>,
    ) -> Result<BitbucketPullRequest> {
        let ctx = self
            .fetch_pr_edit_context(params.workspace, params.repo_slug, params.id)
            .await?;
        let body = build_update_body(
            &ctx,
            params.title,
            params.description,
            params.target_branch,
            None,

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Read {error_text}: Bitbucket names the exact problem (e.g. field validation or state error)
  2. Re-fetch the PR (list/get) and only call update when values.state == OPEN
  3. Re-authenticate with 'but config forge auth' if the status is 401/403 so the token gets pullrequest write scope
  4. On 409/version conflict, re-fetch, re-apply the edit on the fresh version, and retry once

Example fix

// before: blind edit on a possibly-stale PR
client.update_pull_request(&params).await?;

// after: refresh state, edit only while OPEN
let pr = client.get_pull_request(ws, slug, id).await?;
if pr.state != "OPEN" { anyhow::bail!("cannot edit {} PR", pr.state); }
client.update_pull_request(&params).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// only edit PRs that are currently OPEN
let pr = client.get_pull_request(ws, slug, id).await?;
anyhow::ensure!(pr.state == "OPEN", "PR {id} is {} — not editable", pr.state);

Try / catch

match client.update_pull_request(&params).await {
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("409") { reapply_on_fresh_version(&client, &params).await?; }
        else { return Err(e.context("update PR")); }
    }
    ok => ok,
}

Prevention

When it happens

Trigger: Editing title/description/reviewers of a PR that is no longer OPEN (MERGED/DECLINED PRs reject edits), sending an invalid payload (empty title, unknown reviewer UUID), a token without pullrequest write scope, or a 409 from editing a PR whose version moved concurrently.

Common situations: UI holding a stale PR view after someone merged/declined it elsewhere; automation editing PRs after CI merges them; token scopes trimmed after a security review.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/06a536185c37bb4e. Report an issue: GitHub.