gitbutlerapp/gitbutler · error
BUG: No CLI argument for resolved commit id
Error message
BUG: No CLI argument for resolved commit id
What it means
Internal alignment invariant when rejecting 'source is also the target' in the move command: resolved_commits are built one-to-one from the CLI source arguments, so index i into resolved_commits must have a matching args[i]. The expect fires only if that pairing was broken by a code change (resolutions coming from somewhere other than args) - user input cannot desynchronize them.
Source
Thrown at crates/but/src/command/legacy/move.rs:724
Ok(MoveOperation::StackBranch(StackBranchOnOperation {
source_branch,
target_branch,
}))
}
ResolvedSources::Commits {
resolved_commits,
args,
} => {
if let MoveTarget::Commit {
commit: target_commit,
..
} = &target
{
for (i, source_commit) in resolved_commits.iter().enumerate() {
if source_commit.commit_id == target_commit.commit_id {
let unresolved_source = args
.get(i)
.expect("BUG: No CLI argument for resolved commit id");
return Err(bad_input("Source cannot also be target")
.arg_value(unresolved_source.to_string())
.arg_name(format!("--{side}"))
.hint(format!("Trying to move items {side} '{unresolved_source}'? Remove '{unresolved_source}' from '<SOURCES>' and try again!"))
.into());
}
}
}
Ok(MoveOperation::CommitsRelativeTo(
MoveCommitsRelativeToOperation {
sources: resolved_commits,
target,
},
))
}
ResolvedSources::CommittedChanges((source_commit, changes)) => Ok(
MoveOperation::ChangesRelativeTo(MoveChangesRelativeToOperation {View on GitHub (pinned to 2497b8007a)
Solutions
- Report the exact 'but move ...' invocation - unreachable via well-formed input
- Maintainer: zip args with resolved_commits when building them so the pairing is structural rather than index-based
- Add a test moving a commit relative to itself to lock in the error path
Example fix
// before
for (i, source_commit) in resolved_commits.iter().enumerate() {
if source_commit.commit_id == target_commit.commit_id {
let unresolved_source = args.get(i).expect("BUG: No CLI argument for resolved commit id");
// ...
// after - pair each resolution with its argument once, structurally
for (source_commit, unresolved_source) in resolved_commits.iter().zip(args) {
if source_commit.commit_id == target_commit.commit_id {
// error using unresolved_source directly
}
} Defensive patterns
Strategy: validation
Validate before calling
// Construction-side invariant: resolutions and args are built together let resolved: Vec<_> = args.iter().map(|arg| resolve_commit(arg).map(|c| (c, arg.clone()))).collect::<Result<_>>()?; // pairs are now structural; indexing cannot desync
Prevention
- Zip CLI arguments with their resolutions at construction instead of indexing later
- Keep the 1:1 args-to-resolutions assumption documented where resolutions are built
- Add a 'move commit relative to itself' test that exercises this error path
When it happens
Trigger: A refactor resolves commits from a different source than args (workspace stack entries, implicit commits) making the indexes diverge; a multi-source resolution path that skips or reorders args.
Common situations: Changes to move's source resolution (branch, commit, file selection) without updating the paired-args assumption; not reachable through valid CLI usage.
Related errors
- object for prefix exists
- target OID must exist when ahead calculation is enabled
- target OID must exist when merge check is enabled
- BUG: Cannot possibly not have any changes here
- non-empty conflicts map contains a commit
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/1cd29eb467c7ed54.
Report an issue: GitHub.