gitbutlerapp/gitbutler · error
GitHub did not change the review thread resolution
Error message
GitHub did not change the review thread resolution
What it means
After resolving a review thread via GraphQL, the returned thread's `isResolved` flag is compared with the requested `resolved` value; a mismatch means GitHub accepted the request but the thread state does not reflect it. This detects silent mutation failures — e.g. GitHub resolving/unresolving a different thread or ignoring the change — and fails loudly instead of reporting success.
Solutions
- Retry the resolve/unresolve operation after a short delay; eventual consistency often resolves it.
- Verify the thread id is current (re-fetch the review's threads) and that it was not merged away.
- Check for concurrent actors (other users/bots) changing the thread and coordinate.
- If reproducible, inspect the GraphQL response payload and report the inconsistency to GitHub.
Defensive patterns
Strategy: retry
Validate before calling
// Re-fetch the thread and check current state before mutating
let thread = client.get_review_thread(thread_id).await?;
if thread.is_resolved == desired_state { return Ok(()); } // already in target state Try / catch
for attempt in 0..3 {
match client.resolve_thread(thread_id, resolved).await {
Ok(()) => break,
Err(e) if e.to_string().contains("did not change the review thread resolution")
&& attempt < 2 => tokio::time::sleep(Duration::from_millis(500)).await,
Err(e) => return Err(e),
}
} Prevention
- Check thread state before mutating and treat already-satisfied states as success
- Avoid concurrent resolvers on the same thread
- Re-fetch thread ids after a PR merge; old threads may be stale
When it happens
Trigger: Calling thread resolve/unresolve when GitHub's post-mutation query returns a thread whose `isResolved` disagrees with the requested state (race with a concurrent change, wrong thread id resolving a cached/stale node, GitHub-side inconsistency).
Common situations: Concurrent reviewers resolving the same thread simultaneously; operating on a thread id that was deleted or merged mid-operation; GitHub API eventual-consistency lag right after a mutation.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- GitHub GraphQL addPullRequestReviewThreadReply returned no…
- GitHub GraphQL convertPullRequestToDraft returned an empty…
- GitHub GraphQL disablePullRequestAutoMerge returned an…
- GitHub GraphQL enablePullRequestAutoMerge returned an empty…
- GitHub GraphQL markPullRequestReadyForReview returned an…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/e21858392d198a52.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-github/src/client.rs:1229
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Thread {
is_resolved: bool,
}
let query = if resolved {
"mutation($threadId: ID!) { result: resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }"
} else {
"mutation($threadId: ID!) { result: unresolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }"
};
let data: QueryData = self.graphql_query(query, &Variables { thread_id }).await?;
let thread = data
.result
.and_then(|payload| payload.thread)
.context("GitHub returned no review thread after changing its resolution")?;
anyhow::ensure!(
thread.is_resolved == resolved,
"GitHub did not change the review thread resolution"
);
Ok(())
}
/// Reply into an existing review thread, returning the comment it made.
pub async fn add_review_thread_reply(
&self,
thread_id: &str,
body: &str,
) -> Result<PullRequestReviewThreadComment> {
#[derive(Serialize)]
struct Variables<'a> {
#[serde(rename = "threadId")]
thread_id: &'a str,
body: &'a str,
}View on GitHub (pinned to 58e5313667)