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
- Verify the stack still exists via list before unstacking
- Treat unstack as idempotent - if it is already gone, refresh state and continue
- 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
- Make unstack idempotent: verify the stack still exists first
- Guard against double-submitted unstack actions in the UI
- Refresh stack state after any failed mutation
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
- Failed to list GitHub stacks: {}
- Failed to create GitHub stack: {}
- Failed to add to GitHub stack: {}
- Could not restore all review targets after the push failed:
- GitHub GraphQL enablePullRequestAutoMerge returned an empty
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/affd04569c90f26e.
Report an issue: GitHub.