gitbutlerapp/gitbutler · error
Some selected changes no longer match the worktree's…
Error message
Some selected changes no longer match the worktree's uncommitted changes: {} What it means
Before moving, the library replays the selected file/hunk specs against the worktree's current uncommitted changes; specs that no longer match are returned in rejected_specs (with a reason). If any spec is rejected, the whole move aborts so the caller never moves a partially stale selection.
Solutions
- Re-read the current diff and rebuild the selection immediately before moving
- Apply the retry pattern: on this error, refresh the selection from fresh worktree state and re-run (the cleanup code already re-runs sweeps on fresh content)
- Have the user re-confirm the selection after any edit
- Minimize the window between selection capture and move (no formatter/linter hooks in between)
Example fix
// before: reusing a stale selection
let selection = build_selection(&old_diff);
move_uncommitted_changes(ctx, &wt, Some(selection), None)?;
// after: rebuild from the live diff
let selection = build_selection(&worktree_repo.create_wd_tree(0)?.diff()?);
match move_uncommitted_changes(ctx, &wt, Some(selection), None) {
Err(e) if e.to_string().contains("no longer match") => retry_with_fresh_selection(),
r => r,
} Defensive patterns
Strategy: retry
Validate before calling
// rebuild selection from the CURRENT worktree state right before the move let current = worktree_repo.create_wd_tree(0)?; let selection = build_selection(¤t.diff()?); anyhow::ensure!(!selection.is_empty(), "nothing to move");
Try / catch
match move_uncommitted_changes(ctx, &wt, Some(selection), None) {
Err(e) if e.to_string().contains("no longer match") => {
let fresh = rebuild_selection_from_live_diff()?;
move_uncommitted_changes(ctx, &wt, Some(fresh), None)
}
r => r,
} Prevention
- Capture and move in one tight window; never persist selections across sessions
- Disable formatters/autosave during automated moves
- Ask the user to re-confirm after any edit between capture and move
- Re-derive selections from the live diff instead of caching them
When it happens
Trigger: Calling move_uncommitted_changes with a selection captured earlier, while the worktree contents changed in the meantime: files edited/created/deleted, hunks shifted by new edits, line counts no longer matching, or the worktree was rebuilt (create_wd_tree) between capture and move.
Common situations: An agent selecting hunks, then the user (or a formatter/linter) editing the file before the move executes; retries of a move after a first attempt partially ran; long-running sessions where the worktree drifted; concurrent edits from IDE auto-save.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Refusing to check out conflicted commit
- Visible worktree changed since the editor was created…
- Branch ' ' already exists
- Branch ' ' cannot be created: the target commit ( ) already…
- `but setup` cannot run from a linked worktree; run it from…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/6a7f8dbc71c76074.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/worktrees.rs:203
worktree_repo.create_wd_tree(0)?
}
Some(selection) => {
if selection.is_empty() {
bail!("No changes were selected to move");
}
let outcome = but_core::tree::create_tree(
worktree_repo,
worktree_head_tree,
selection,
context_lines,
)?;
let rejected: Vec<_> = outcome
.rejected_specs
.iter()
.map(|(reason, spec)| format!("{reason:?}: {}", spec.path))
.collect();
if !rejected.is_empty() {
bail!(
"Some selected changes no longer match the worktree's uncommitted changes: {}",
rejected.join(", ")
);
}
outcome.destination_tree.context("No changes to move")?
}
};
if moved_tree == worktree_head_tree {
bail!("No changes to move");
}
let destination_outcome = but_core::worktree::safe_checkout_from_head(
moved_tree,
main_repo,
but_core::worktree::checkout::Options {
merge_base_override: Some(worktree_head_tree),
allow_uncommitted_changes_to_conflict_with_new_head: true,
..Default::default()View on GitHub (pinned to 58e5313667)