gitbutlerapp/gitbutler · error · anyhow::Error

Failed to unstack GitHub stack: {}

Error message

Failed to unstack GitHub stack: {}

What it means

POST /repos/{o}/{r}/stacks/{stack_number}/unstack returned a non-2xx status; the stack could not be dissolved.

Source

Thrown at crates/but-github/src/stacks.rs:353

            bail!(
                "Failed to add to GitHub stack: {}",
                response_error(response).await
            );
        }
        Ok(())
    }

    async fn unstack(&self, owner: &str, repo: &str, stack_number: i64) -> Result<()> {
        let response = self
            .client
            .post(format!(
                "{}/repos/{owner}/{repo}/stacks/{stack_number}/unstack",
                self.base_url
            ))
            .send()
            .await?;
        if !response.status().is_success() {
            bail!(
                "Failed to unstack GitHub stack: {}",
                response_error(response).await
            );
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn stack(number: i64, members: &[i64]) -> Stack {
        Stack {
            number,
            pull_requests: members
                .iter()
                .map(|number| StackPullRequest {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Verify the stack still exists via list before unstacking
  2. Treat unstack as idempotent - if it is already gone, refresh state and continue
  3. Address auth or rate-limit causes indicated by the wrapped error
Defensive patterns

Strategy: try-catch

Validate before calling

let stacks = client.list_stacks(owner, repo, pr_number).await?.unwrap_or_default();
if !stacks.iter().any(|s| s.number == stack_number) {
    return Ok(()); // already unstacked - treat as success
}

Try / catch

match client.unstack(owner, repo, stack_number).await {
    Err(e) if e.to_string().starts_with("Failed to unstack") => {
        let stacks = client.list_stacks(owner, repo, pr_number).await?;
        if !stacks.iter().any(|s| s.number == stack_number) { Ok(()) } else { Err(e) }
    }
    other => other,
}

Prevention

When it happens

Trigger: The stack number does not exist anymore; the stack was already unstacked; the acting user lacks permission; rate limiting.

Common situations: Duplicate unstack runs (double-click, retried job); stale UI state; permission changes mid-session.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/affd04569c90f26e. Report an issue: GitHub.