gitbutlerapp/gitbutler · error

Could not discard all selected changes: {refused_paths}

Error message

Could not discard all selected changes: {refused_paths}

What it means

`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.

Source

Thrown at crates/but/src/command/legacy/discard.rs:511

                    let new_commit = tx.discard_changes_from_commit(source.commit_id, changes)?;
                    DiscardOutcome::CommittedFiles {
                        source,
                        paths,
                        new_commit: new_commit.into(),
                    }
                }
                ExecutableDiscardOperation::Uncommitted { paths, changes } => {
                    let refused = but_workspace::discard_workspace_changes(
                        tx.repo(),
                        changes,
                        tx.context_lines(),
                    )?;
                    if !refused.is_empty() {
                        let refused_paths = refused
                            .iter()
                            .map(|change| change.path.as_bstr())
                            .join(", ");
                        bail!("Could not discard all selected changes: {refused_paths}");
                    }
                    DiscardOutcome::Uncommitted { paths }
                }
            };

            Ok(Commit(outcome))
        },
    )?;

    if let DiscardOutcome::Commits {
        replaced_commits, ..
    } = &mut outcome
    {
        *replaced_commits = std::mem::take(&mut ws.replaced_commits);
    }

    Ok((outcome, ws))
}

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Re-select and re-run the discard so the diff specs are computed from the current worktree state.
  2. Check `but status` / `git status`: the refused paths may already be gone or changed; discard what remains individually.
  3. Close other programs touching the worktree (editors, file watchers, other `but` instances) and retry.
Defensive patterns

Strategy: retry

Validate before calling

# Refresh the selection right before discarding; refused paths are ones no longer in the current diff
but status | grep -F '<path>' || { echo 'path no longer dirty'; exit 2; }

Try / catch

// When calling discard APIs programmatically, recompute specs and retry once on refusal
let refused = discard_workspace_changes(repo, specs.clone(), context_lines)?;
if !refused.is_empty() {
    let specs = recompute_specs_from_current_status(repo)?; // selection was stale
    let refused = discard_workspace_changes(repo, specs, context_lines)?;
    // surface any remaining refusals to the caller
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/4e0bd2405b0e1c00. Report an issue: GitHub.