gitbutlerapp/gitbutler · error
Failed to create pull request comment
Error message
Failed to create pull request comment: {} What it means
This error comes from the PR comment creation path: POSTing `/repos/{owner}/{repo}/issues/{pr_number}/comments` (or the equivalent PR comment endpoint) with a `CommentBody`. Any non-success status is wrapped via `response_error` and bailed, so no `PullRequestComment` is produced and the comment was not created.
Solutions
- Read the embedded status/body: 403 with 'locked' means comments are disabled — skip posting instead of retrying.
- Validate the body is non-empty before calling; trim whitespace and reject blank comments client-side.
- Verify the token has write access to issues/conversations and the repo coordinates are correct.
- Respect `Retry-After`/rate-limit headers and retry later with backoff for 403 rate-limit or 5xx.
- Confirm the issue number is the PR's number in that exact repository.
Example fix
// before
let body = body.trim();
let comment = client.create_pull_request_comment(owner, repo, pr, body).await?; // 422 on empty
// after
let body = body.trim();
if body.is_empty() {
return Ok(None); // nothing to post
}
let comment = client.create_pull_request_comment(owner, repo, pr, body).await?; Defensive patterns
Strategy: validation
Validate before calling
if body.trim().is_empty() { return Err(anyhow!("comment body must not be empty")); }
let pr = client.get_pull_request(owner, repo, pr_number).await?; // 404 check
if client.issue_comments_locked(owner, repo).await.unwrap_or(false) {
return Err(anyhow!("comments are locked on {owner}/{repo}"));
} Type guard
fn postable(body: &str) -> bool { !body.trim().is_empty() } Try / catch
match client.create_pull_request_comment(owner, repo, pr, body).await {
Ok(c) => c,
Err(e) if e.to_string().contains("403") && e.to_string().contains("locked") => {
warn!("comments locked; skipping");
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Reject empty/whitespace comment bodies before the API call.
- Check that the repo does not lock/disable issue comments.
- Honor rate-limit headers and back off instead of hammering retries.
- Verify token scope covers issues/conversations write.
When it happens
Trigger: Creating a comment on a PR when GitHub returns non-2xx — 404 (wrong owner/repo/issue number), 403 (issues/conversations write denied, disabled issue comments, or rate limit), 401 (invalid token), 422 (empty or invalid body).
Common situations: Posting to a repo where issue comments are locked/disabled by maintainers; token without `repo` scope on a private repo; empty comment body after trimming; PR number mixed up with an internal stack/branch id; rate limit exhausted after a bulk operation.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to delete comment
- Failed to request reviewers
- Failed to update comment
- Failed to withdraw review request
- Anthropic API error ( )
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/3fa407b1a8f3c7cd.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-github/src/client.rs:1398
#[derive(Serialize)]
struct CommentBody<'a> {
body: &'a str,
}
let url = format!(
"{}/repos/{}/{}/issues/{}/comments",
self.base_url, owner, repo, pr_number
);
let response = self
.client
.post(&url)
.json(&CommentBody { body })
.send()
.await?;
if !response.status().is_success() {
bail!(
"Failed to create pull request comment: {}",
response_error(response).await
);
}
let comment: GitHubIssueComment = response.json().await?;
Ok(comment.into())
}
/// Update the information of a given PR.
///
/// This is used e.g. to update the description footers for stacked reviews.
pub async fn update_pull_request(
&self,
params: &UpdatePullRequestParams<'_>,
) -> Result<PullRequest> {
#[derive(Serialize)]
struct UpdatePullRequestBody<'a> {View on GitHub (pinned to 58e5313667)