{"record":{"id":"222ba8e688159bc9","repo":"gitbutlerapp/gitbutler","slug":"failed-to-add-labels","errorCode":null,"errorMessage":"Failed to add labels: {}","messagePattern":"Failed to add labels: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-github/src/client.rs","lineNumber":802,"sourceCode":"        #[derive(Serialize)]\n        struct AddLabelsBody<'a> {\n            labels: &'a [String],\n        }\n\n        let url = format!(\n            \"{}/repos/{}/{}/issues/{}/labels\",\n            self.base_url, owner, repo, pr_number\n        );\n\n        let response = self\n            .client\n            .post(&url)\n            .json(&AddLabelsBody { labels })\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            bail!(\"Failed to add labels: {}\", response_error(response).await);\n        }\n\n        let labels: Vec<GitHubPrLabelApi> = response.json().await?;\n        Ok(labels.into_iter().map(Into::into).collect())\n    }\n\n    pub async fn remove_label_from_pull_request(\n        &self,\n        owner: &str,\n        repo: &str,\n        pr_number: i64,\n        label: &str,\n    ) -> Result<()> {\n        let url = label_removal_url(&self.base_url, owner, repo, pr_number, label)?;\n\n        let response = self.client.delete(url).send().await?;\n\n        if !response.status().is_success() {","sourceCodeStart":784,"sourceCodeEnd":820,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-github/src/client.rs#L784-L820","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the detailed error text appended after the status to identify 401/403/404/422","Ensure the token has write access to pull requests on that repo","Validate label names (no invalid characters, non-empty) before the call"],"exampleFix":"// before\nclient.add_labels_to_pull_request(owner, repo, pr, vec![\"need review!\"]).await?;\n// after\nclient.add_labels_to_pull_request(owner, repo, pr, vec![\"need-review\"]).await?;","handlingStrategy":"try-catch","validationCode":"if labels.iter().any(|l| l.trim().is_empty()) {\n    return Err(\"labels must be non-empty\".into());\n}\n// also verify write access beforehand if possible via GET /repos/{o}/{r}/collaborator perms","typeGuard":null,"tryCatchPattern":"match client.add_labels_to_pull_request(owner, repo, pr, labels).await {\n    Ok(applied) => applied,\n    Err(e) if e.to_string().contains(\"403\") => {\n        eprintln!(\"token lacks write access or rate-limited; not retrying immediately\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Use tokens with pull-request write permission for mutations","Sanitize/validate label names before sending","Back off on 403 rate-limit responses rather than retrying in a tight loop"],"tags":["github","api","labels","http","permissions"],"backgroundTag":"api-error-response","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}