{"record":{"id":"4c4ca45298347785","repo":"gitbutlerapp/gitbutler","slug":"cannot-compute-diff-specs-for-worktree-name","errorCode":null,"errorMessage":"Cannot compute diff specs for worktree `{name}`","messagePattern":"Cannot compute diff specs for worktree `(.+?)`","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/utils/diff_specs.rs","lineNumber":85,"sourceCode":"                    },\n                id: _,\n            } => self.push_changes_from_committed_file(*commit_id, path.as_ref()),\n            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() {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/utils/diff_specs.rs#L67-L103","documentation":"Thrown by the diff-spec builder in the `but` crate when a change selection identifying a whole worktree (`CliId::Worktree`) reaches `push_changes`. By design, worktree selections are expanded into their individual files during selection resolution, so the builder only ever expects hunks that already come from its own repository. Seeing this error means a caller passed an unresolved worktree id straight to the builder, bypassing the resolution step.","triggerScenarios":"Calling `DiffSpecBuilder::push_changes` (or a wrapper around it) with a `CliId::Worktree { name, .. }` value instead of the resolved per-file/per-hunk selections, e.g. a new CLI command or TUI flow that forwards raw CLI selections directly into diff-spec computation.","commonSituations":"New commands that accept arbitrary `CliId` values but forget to run the resolver that expands worktrees into files; refactors that route selections around the resolution step; unit tests that hand raw ids to the builder.","solutions":["Route the selection through the standard resolution step that expands worktrees into file-level selections before calling push_changes","If the operation supports worktree sources, validate the selection and construct the builder via `DiffSpecBuilder::for_change_source` instead of the main-worktree constructor","If you never call the builder directly, report a bug against the command that produced the unresolved worktree selection"],"exampleFix":"// before\nlet mut builder = DiffSpecBuilder::new(&repo);\nbuilder.push_changes(&cli_id)?; // cli_id is CliId::Worktree\n\n// after: expand aggregate selections during resolution first\nlet resolved = resolve_selection(&repo, &cli_id)?; // worktree -> its files/hunks\nlet mut builder = DiffSpecBuilder::new(&repo);\nbuilder.push_changes(&resolved)?;","handlingStrategy":"validation","validationCode":"// before computing diff specs, expand aggregate selections\nif matches!(cli_id, CliId::Worktree { .. } | CliId::Stack { .. }) {\n    let cli_id = resolve_selection(&repo, &cli_id)?; // expands worktree/stack into files\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 worktree\") => {\n        // selection was not resolved; expand it and retry\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Always run selection resolution before handing ids to DiffSpecBuilder","Use exhaustive matches over CliId in callers so new aggregate variants fail at compile time, not at runtime"],"tags":["rust","gitbutler","diff-specs","worktree","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"}