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

  1. Retry the resolve/unresolve operation after a short delay; eventual consistency often resolves it.
  2. Verify the thread id is current (re-fetch the review's threads) and that it was not merged away.
  3. Check for concurrent actors (other users/bots) changing the thread and coordinate.
  4. 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

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


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)