gitbutlerapp/gitbutler · error · anyhow::Error

Refusing to remove label with degenerate name {label:?}

Error message

Refusing to remove label with degenerate name {label:?}

What it means

Label removal builds DELETE /repos/{o}/{r}/issues/{n}/labels/{name} by pushing the label into the URL path. The url crate's PathSegmentsMut::push silently drops '.' and '..' segments, which would degrade the URL into GitHub's remove-ALL-labels endpoint and wipe every label on the issue. The guard therefore refuses empty, '.', and '..' label names instead of encoding them.

Source

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

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

/// Build `DELETE /repos/{o}/{r}/issues/{n}/labels/{name}` with the label name
/// percent-encoded. `PathSegmentsMut::push` silently *drops* `.` and `..`
/// segments, which would degrade this into GitHub's remove-ALL-labels
/// endpoint — refuse those names instead of encoding them.
fn label_removal_url(
    base_url: &str,
    owner: &str,
    repo: &str,
    pr_number: i64,
    label: &str,
) -> Result<reqwest::Url> {
    if matches!(label, "" | "." | "..") {
        bail!("Refusing to remove label with degenerate name {label:?}");
    }

    let mut url = reqwest::Url::parse(&format!(
        "{base_url}/repos/{owner}/{repo}/issues/{pr_number}/labels"
    ))?;
    url.path_segments_mut()
        .map_err(|()| anyhow::anyhow!("Invalid GitHub base URL"))?
        .push(label);
    Ok(url)
}

/// A submitted review on a pull request, from `GET /pulls/{n}/reviews`.
#[derive(Debug, Serialize)]
pub struct PullRequestReview {
    pub id: i64,
    pub author: Option<GitHubUser>,
    /// GitHub state string: `APPROVED`, `CHANGES_REQUESTED`, `COMMENTED`,
    /// `DISMISSED`, or `PENDING` (the caller's own unsubmitted draft).

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Filter degenerate names out of the label list before calling remove_label
  2. Validate label names at input time - GitHub labels cannot be empty anyway
  3. Fix the upstream string handling that produced the empty or '.' value

Example fix

// before
for label in labels {
    client.remove_label(owner, repo, pr, &label).await?;
}

// after
for label in labels.iter().filter(|l| !matches!(l.as_str(), "" | "." | "..")) {
    client.remove_label(owner, repo, pr, label).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

let safe: Vec<_> = labels.iter().filter(|l| is_safe_label_name(l)).cloned().collect();
if safe.len() != labels.len() {
    anyhow::bail!("refusing label removal: degenerate label names present");
}

Type guard

fn is_safe_label_name(label: &str) -> bool {
    !matches!(label, "" | "." | "..")
}

Prevention

When it happens

Trigger: Calling label removal with a name that is "", ".", or ".." - typically unvalidated user input, empty pieces produced by splitting a string on separators, or config placeholders.

Common situations: Parsing comma-separated label lists with trailing separators; UI forms that allow empty submits; label names derived programmatically from branch or file names.

Related errors


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