{"record":{"id":"4e0bd2405b0e1c00","repo":"gitbutlerapp/gitbutler","slug":"could-not-discard-all-selected-changes-refused-p","errorCode":null,"errorMessage":"Could not discard all selected changes: {refused_paths}","messagePattern":"Could not discard all selected changes: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/discard.rs","lineNumber":511,"sourceCode":"                    let new_commit = tx.discard_changes_from_commit(source.commit_id, changes)?;\n                    DiscardOutcome::CommittedFiles {\n                        source,\n                        paths,\n                        new_commit: new_commit.into(),\n                    }\n                }\n                ExecutableDiscardOperation::Uncommitted { paths, changes } => {\n                    let refused = but_workspace::discard_workspace_changes(\n                        tx.repo(),\n                        changes,\n                        tx.context_lines(),\n                    )?;\n                    if !refused.is_empty() {\n                        let refused_paths = refused\n                            .iter()\n                            .map(|change| change.path.as_bstr())\n                            .join(\", \");\n                        bail!(\"Could not discard all selected changes: {refused_paths}\");\n                    }\n                    DiscardOutcome::Uncommitted { paths }\n                }\n            };\n\n            Ok(Commit(outcome))\n        },\n    )?;\n\n    if let DiscardOutcome::Commits {\n        replaced_commits, ..\n    } = &mut outcome\n    {\n        *replaced_commits = std::mem::take(&mut ws.replaced_commits);\n    }\n\n    Ok((outcome, ws))\n}","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/legacy/discard.rs#L493-L529","documentation":"`but discard` feeds the selected uncommitted changes (DiffSpecs) to `but_workspace::discard_workspace_changes`, which re-reads the current worktree diff and refuses any spec whose path (and rename previous_path) no longer matches a live worktree change (see the `dropped.push(spec)` branch in discard_worktree_changes.rs). The CLI bails listing the refused paths; the bail aborts the surrounding transaction, so no partial discard is committed.","triggerScenarios":"The worktree changed between computing the diff specs (e.g. an interactive selection) and executing the discard: the file was reverted, committed, or its hunks shifted so no matching entry exists in the fresh worktree status; rename specs whose previous_path no longer matches are also refused.","commonSituations":"Concurrent editors/IDE formatters touching files while discarding; running the same discard selection twice (the second run's specs are stale); another GitButler client or agent mutating the worktree between selection and execution.","solutions":["Re-select and re-run the discard so the diff specs are computed from the current worktree state.","Check `but status` / `git status`: the refused paths may already be gone or changed; discard what remains individually.","Close other programs touching the worktree (editors, file watchers, other `but` instances) and retry."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"# Refresh the selection right before discarding; refused paths are ones no longer in the current diff\nbut status | grep -F '<path>' || { echo 'path no longer dirty'; exit 2; }","typeGuard":null,"tryCatchPattern":"// When calling discard APIs programmatically, recompute specs and retry once on refusal\nlet refused = discard_workspace_changes(repo, specs.clone(), context_lines)?;\nif !refused.is_empty() {\n    let specs = recompute_specs_from_current_status(repo)?; // selection was stale\n    let refused = discard_workspace_changes(repo, specs, context_lines)?;\n    // surface any remaining refusals to the caller\n}","preventionTips":["Compute diff specs immediately before discarding, never from cached status.","Avoid concurrent worktree mutations (editors, watchers, other `but` instances) while a discard runs.","After a refusal, refresh `but status` and discard the remaining paths individually."],"tags":["cli","discard","worktree","race-condition","gitbutler"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}