gitbutlerapp/gitbutler · error

Failed to add labels

Error message

Failed to add labels: {}

What it means

The GitHub REST call to POST labels onto an issue/PR returned a non-success HTTP status. The message embeds response_error(response), which carries the API's own explanation of the refusal (e.g. 404 for a wrong owner/repo/PR, 403 for missing permissions, 422 for invalid labels).

Solutions

  1. Read the detailed error text appended after the status to identify 401/403/404/422
  2. Ensure the token has write access to pull requests on that repo
  3. Validate label names (no invalid characters, non-empty) before the call

Example fix

// before
client.add_labels_to_pull_request(owner, repo, pr, vec!["need review!"]).await?;
// after
client.add_labels_to_pull_request(owner, repo, pr, vec!["need-review"]).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

if labels.iter().any(|l| l.trim().is_empty()) {
    return Err("labels must be non-empty".into());
}
// also verify write access beforehand if possible via GET /repos/{o}/{r}/collaborator perms

Try / catch

match client.add_labels_to_pull_request(owner, repo, pr, labels).await {
    Ok(applied) => applied,
    Err(e) if e.to_string().contains("403") => {
        eprintln!("token lacks write access or rate-limited; not retrying immediately");
        Vec::new()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling add_labels_to_pull_request where the PR doesn't exist (404), the token lacks write/pull-requests permission (403), the label names are invalid, or a rate limit/5xx occurs.

Common situations: Token is read-only (no 'repo' scope or fine-grained write access to pull requests); PR closed/deleted; label names containing characters GitHub rejects; CI hitting secondary rate limits.

Related errors


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

Appendix: source

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

        #[derive(Serialize)]
        struct AddLabelsBody<'a> {
            labels: &'a [String],
        }

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

        let response = self
            .client
            .post(&url)
            .json(&AddLabelsBody { labels })
            .send()
            .await?;

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

        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() {

View on GitHub (pinned to 58e5313667)