{"record":{"id":"facb08ba2d01e0d1","repo":"gitbutlerapp/gitbutler","slug":"cannot-compute-diff-specs-for-stacks","errorCode":null,"errorMessage":"Cannot compute diff specs for stacks","messagePattern":"Cannot compute diff specs for stacks","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/utils/diff_specs.rs","lineNumber":88,"sourceCode":"            CliId::Branch(branch) => {\n                anyhow::bail!(\"Cannot compute diff specs for branch `{}`\", branch.name)\n            }\n            CliId::Commit {\n                commit:\n                    CommitId {\n                        commit_id,\n                        change_id: _,\n                    },\n                id: _,\n            } => self.push_changes_from_commit(*commit_id),\n            CliId::Uncommitted { id: _ } => self.push_changes_from_uncommitted_area(),\n            // A worktree is expanded into its files during resolution, so the\n            // builder only ever sees hunks that already come from its own repo.\n            CliId::Worktree { name, .. } => {\n                anyhow::bail!(\"Cannot compute diff specs for worktree `{name}`\")\n            }\n            CliId::Stack { .. } => {\n                anyhow::bail!(\"Cannot compute diff specs for stacks\")\n            }\n        }\n    }\n\n    pub fn push_changes_from_uncommitted(\n        &mut self,\n        uncommitted: &UncommittedHunkOrFile,\n    ) -> anyhow::Result<()> {\n        // Specs are built against one checkout's repository, so a hunk from\n        // another would silently address the wrong files. Operations that support\n        // worktree sources validate the selection and construct the builder via\n        // [`Self::for_change_source`]; the rest read the main worktree and refuse\n        // worktree hunks here.\n        if uncommitted.source != self.source {\n            if let Some(name) = uncommitted.source.worktree_name() {\n                anyhow::bail!(\"Cannot operate on uncommitted changes in worktree {name} yet\");\n            }\n            anyhow::bail!(","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/utils/diff_specs.rs#L70-L106","documentation":"Thrown by the diff-spec builder in the `but` crate when a change selection identifying a whole stack (`CliId::Stack`) reaches `push_changes`. Stacks are aggregate selections that must be expanded into their constituent commits/hunks during resolution; the builder only accepts selections resolvable against one repository checkout. Hitting this bail means an unexpanded stack id was passed directly.","triggerScenarios":"Calling `DiffSpecBuilder::push_changes` with a `CliId::Stack { .. }` value, bypassing the resolution step that flattens a stack into per-commit selections.","commonSituations":"CLI/TUI commands that accept stack selections for operations that only compute specs from commits or uncommitted hunks; refactors that skip resolution; tests passing raw ids.","solutions":["Run the selection through the standard resolution step that expands stacks into commit-level selections before building specs","Use the validated operation path that supports the selection kind instead of calling the builder directly","If reached via an existing command, report a bug with the exact selection you passed"],"exampleFix":"// before\nlet mut builder = DiffSpecBuilder::new(&repo);\nbuilder.push_changes(&CliId::Stack { .. })?;\n\n// after: expand the stack into its commits first\nlet resolved = resolve_selection(&repo, &cli_id)?; // stack -> its commits\nlet mut builder = DiffSpecBuilder::new(&repo);\nbuilder.push_changes(&resolved)?;","handlingStrategy":"validation","validationCode":"if matches!(cli_id, CliId::Stack { .. }) {\n    let cli_id = resolve_selection(&repo, &cli_id)?; // expands the stack into commits\n    // ... proceed with the resolved selection\n}","typeGuard":"fn is_pushable_diff_spec_source(id: &CliId) -> bool {\n    !matches!(id, CliId::Worktree { .. } | CliId::Stack { .. })\n}","tryCatchPattern":"match builder.push_changes(&selection) {\n    Ok(()) => { /* continue */ }\n    Err(err) if err.to_string().contains(\"Cannot compute diff specs for stacks\") => {\n        // expand the stack selection and retry\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Route stack selections through resolution before spec computation","Add unit tests covering every CliId variant your command accepts"],"tags":["rust","gitbutler","diff-specs","stacks","cli","programmer-error"],"backgroundTag":"unsupported-selection-type","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T21:17:23.414Z"}