gitbutlerapp/gitbutler · error

Failed to remove label

Error message

Failed to remove label: {}

What it means

The label-removal function DELETEs a single label from a PR; any non-success HTTP status bails with this message and the response error body. Means GitHub refused the removal — usually the label or PR doesn't exist, or the token lacks write access.

Solutions

  1. Check the appended response error for the exact status/cause
  2. Ensure the token has write (pull-requests) permission
  3. URL-encode the label name when constructing the removal URL; treat 404 as 'already removed' if removal is idempotent in your flow

Example fix

// before
let url = format!("{base}/repos/{o}/{r}/issues/{n}/labels/{label}"); // raw label
// after
let url = format!("{base}/repos/{o}/{r}/issues/{n}/labels/{}", urlencode(label));
Defensive patterns

Strategy: try-catch

Validate before calling

let encoded = urlencode(label);
// optionally check label exists first:
// GET /repos/{o}/{r}/labels/{encoded} -> 404 means nothing to remove

Try / catch

match client.remove_label_from_pull_request(owner, repo, pr, label).await {
    Ok(()) => (),
    Err(e) if e.to_string().contains("404") => (), // already gone: idempotent OK
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the label removal API for a PR the token can't write to (403), a PR that doesn't exist (404), or a label name needing URL encoding that produced a bad URL (422/404).

Common situations: Read-only token used for a mutating call; label already removed concurrently (404); label name with spaces/slashes not URL-encoded; rate limiting.

Related errors


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

Appendix: source

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

        }

        let labels: Vec<GitHubPrLabelApi> = response.json().await?;
        Ok(labels.into_iter().map(Into::into).collect())
    }

    pub async fn remove_label_from_pull_request(
        &self,
        owner: &str,
        repo: &str,
        pr_number: i64,
        label: &str,
    ) -> Result<()> {
        let url = label_removal_url(&self.base_url, owner, repo, pr_number, label)?;

        let response = self.client.delete(url).send().await?;

        if !response.status().is_success() {
            bail!("Failed to remove label: {}", response_error(response).await);
        }

        Ok(())
    }

    /// List users who can be assigned (and requested for review) on the repo.
    pub async fn list_assignable_users(&self, owner: &str, repo: &str) -> Result<Vec<GitHubUser>> {
        let url = format!("{}/repos/{}/{}/assignees", self.base_url, owner, repo);

        Ok(self
            .get_all_pages::<GitHubApiUser>(&url, 10)
            .await?
            .into_iter()
            .map(GitHubUser::from)
            .collect())
    }

    /// Copilot's reviewer account, or `None` where the forge has none. It can

View on GitHub (pinned to 58e5313667)