gitbutlerapp/gitbutler · error · anyhow::Error

Bitbucket does not support reopening a declined pull request

Error message

Bitbucket does not support reopening a declined pull request via the API.

What it means

In `update_review()`, the Bitbucket arm rejects `ReviewState::Open` up front: Bitbucket Cloud's API cannot reopen a declined PR through the endpoints used here (closing maps to `pr::decline`, edits map to `pr::update`). The check runs before any HTTP call, so the PR is left untouched.

Source

Thrown at crates/but-forge/src/review.rs:2013

            // a `state` field. Map the forge-agnostic ReviewState onto that.
            let state_event = state.as_ref().map(|s| s.as_gitlab_state_event());
            let params = but_gitlab::UpdateMergeRequestParams {
                project_id,
                mr_iid,
                title: title.as_deref(),
                description: body.as_deref(),
                target_branch: target_base.as_deref(),
                state_event,
            };
            but_gitlab::mr::update(preferred_account, params, storage).await?;
            Ok(())
        }
        ForgeName::Bitbucket => {
            let preferred_account = preferred_forge_user
                .as_ref()
                .and_then(|user| user.bitbucket());
            if matches!(state, Some(ReviewState::Open)) {
                return Err(anyhow::anyhow!(
                    "Bitbucket does not support reopening a declined pull request via the API."
                ));
            }
            if title.is_some() || body.is_some() || target_base.is_some() {
                let id = review_number
                    .try_into()
                    .context("PR: Failed to cast usize to i64, somehow")?;
                let params = but_bitbucket::UpdatePullRequestParams {
                    workspace: owner,
                    repo_slug: repo,
                    id,
                    title: title.as_deref(),
                    description: body.as_deref(),
                    target_branch: target_base.as_deref(),
                };
                but_bitbucket::pr::update(preferred_account, params, storage).await?;
            }
            if matches!(state, Some(ReviewState::Closed)) {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Don't offer 'reopen' for declined Bitbucket PRs — hide the action when the forge is Bitbucket.
  2. Create a new PR from the same branches instead of reopening the declined one.
  3. Pass `state: None` when you only want to edit title/description/target branch on Bitbucket.
  4. If reopening is a hard requirement, call Bitbucket's REST API outside but-forge.

Example fix

// before
update_review(&user, &info, number,
    ReviewUpdatePayload { state: Some(ReviewState::Open), ..Default::default() },
    &storage).await?;

// after
if matches!(info.forge, ForgeName::Bitbucket)
    && matches!(payload.state, Some(ReviewState::Open))
{
    // reopening a declined PR is unsupported; offer 'new PR from branch' instead
    return Ok(());
}
update_review(&user, &info, number, payload, &storage).await?;
Defensive patterns

Strategy: validation

Validate before calling

use but_forge::forge::ForgeName;
use but_forge::review::ReviewState;

if matches!(info.forge, ForgeName::Bitbucket)
    && matches!(payload.state, Some(ReviewState::Open))
{
    // hide 'reopen'; Bitbucket declined PRs cannot reopen via this API
}

Type guard

fn reopen_supported(forge: &but_forge::forge::ForgeName) -> bool {
    !matches!(forge, ForgeName::Bitbucket)
}

Try / catch

match update_review(&user, &info, number, payload, &storage).await {
    Ok(()) => {}
    Err(e) if e.to_string().contains("does not support reopening") => {
        // surface 'create a new PR from the branch' guidance instead
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `update_review` with `forge: bitbucket` and `ReviewUpdatePayload.state == Some(ReviewState::Open)` — typically a 'Reopen' action on a declined Bitbucket PR. Title/body/target-base edits and `Some(ReviewState::Closed)` (decline) proceed normally.

Common situations: UIs reusing GitHub semantics ('reopen after close') across forges; users expecting declined Bitbucket PRs to be reopenable; automations that normalize state transitions per forge.

Related errors


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