{"record":{"id":"affd04569c90f26e","repo":"gitbutlerapp/gitbutler","slug":"failed-to-unstack-github-stack","errorCode":null,"errorMessage":"Failed to unstack GitHub stack: {}","messagePattern":"Failed to unstack GitHub stack: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-github/src/stacks.rs","lineNumber":353,"sourceCode":"            bail!(\n                \"Failed to add to GitHub stack: {}\",\n                response_error(response).await\n            );\n        }\n        Ok(())\n    }\n\n    async fn unstack(&self, owner: &str, repo: &str, stack_number: i64) -> Result<()> {\n        let response = self\n            .client\n            .post(format!(\n                \"{}/repos/{owner}/{repo}/stacks/{stack_number}/unstack\",\n                self.base_url\n            ))\n            .send()\n            .await?;\n        if !response.status().is_success() {\n            bail!(\n                \"Failed to unstack GitHub stack: {}\",\n                response_error(response).await\n            );\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn stack(number: i64, members: &[i64]) -> Stack {\n        Stack {\n            number,\n            pull_requests: members\n                .iter()\n                .map(|number| StackPullRequest {","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-github/src/stacks.rs#L335-L371","documentation":"POST /repos/{o}/{r}/stacks/{stack_number}/unstack returned a non-2xx status; the stack could not be dissolved.","triggerScenarios":"The stack number does not exist anymore; the stack was already unstacked; the acting user lacks permission; rate limiting.","commonSituations":"Duplicate unstack runs (double-click, retried job); stale UI state; permission changes mid-session.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let stacks = client.list_stacks(owner, repo, pr_number).await?.unwrap_or_default();\nif !stacks.iter().any(|s| s.number == stack_number) {\n    return Ok(()); // already unstacked - treat as success\n}","typeGuard":null,"tryCatchPattern":"match client.unstack(owner, repo, stack_number).await {\n    Err(e) if e.to_string().starts_with(\"Failed to unstack\") => {\n        let stacks = client.list_stacks(owner, repo, pr_number).await?;\n        if !stacks.iter().any(|s| s.number == stack_number) { Ok(()) } else { Err(e) }\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["github","stacks","rest-api","pull-request"],"backgroundTag":"github-api-request-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}