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
- Filter degenerate names out of the label list before calling remove_label
- Validate label names at input time - GitHub labels cannot be empty anyway
- 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
- Sanitize label lists at input time: reject empty strings before splitting logic can create them
- Never construct path-bearing URLs from raw user strings without segment validation
- Remember url::PathSegmentsMut silently drops '.' and '..' segments
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
- Repository labels for forge {forge:?} are not implemented ye
- Review labels for forge {forge:?} are not implemented yet.
- Invalid playlist URL
- BUG: Sensitive data cannot be serialized - it needs to be ex
- existing GitMeta key '{key}' is not a string
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/3c9b20ece7768653.
Report an issue: GitHub.