{"record":{"id":"1cd29eb467c7ed54","repo":"gitbutlerapp/gitbutler","slug":"bug-no-cli-argument-for-resolved-commit-id","errorCode":null,"errorMessage":"BUG: No CLI argument for resolved commit id","messagePattern":"BUG: No CLI argument for resolved commit id","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/move.rs","lineNumber":724,"sourceCode":"            Ok(MoveOperation::StackBranch(StackBranchOnOperation {\n                source_branch,\n                target_branch,\n            }))\n        }\n        ResolvedSources::Commits {\n            resolved_commits,\n            args,\n        } => {\n            if let MoveTarget::Commit {\n                commit: target_commit,\n                ..\n            } = &target\n            {\n                for (i, source_commit) in resolved_commits.iter().enumerate() {\n                    if source_commit.commit_id == target_commit.commit_id {\n                        let unresolved_source = args\n                            .get(i)\n                            .expect(\"BUG: No CLI argument for resolved commit id\");\n                        return Err(bad_input(\"Source cannot also be target\")\n                            .arg_value(unresolved_source.to_string())\n                            .arg_name(format!(\"--{side}\"))\n                            .hint(format!(\"Trying to move items {side} '{unresolved_source}'? Remove '{unresolved_source}' from '<SOURCES>' and try again!\"))\n                            .into());\n                    }\n                }\n            }\n\n            Ok(MoveOperation::CommitsRelativeTo(\n                MoveCommitsRelativeToOperation {\n                    sources: resolved_commits,\n                    target,\n                },\n            ))\n        }\n        ResolvedSources::CommittedChanges((source_commit, changes)) => Ok(\n            MoveOperation::ChangesRelativeTo(MoveChangesRelativeToOperation {","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/command/legacy/move.rs#L706-L742","documentation":"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.","triggerScenarios":"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.","commonSituations":"Changes to move's source resolution (branch, commit, file selection) without updating the paired-args assumption; not reachable through valid CLI usage.","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"],"exampleFix":"// before\nfor (i, source_commit) in resolved_commits.iter().enumerate() {\n    if source_commit.commit_id == target_commit.commit_id {\n        let unresolved_source = args.get(i).expect(\"BUG: No CLI argument for resolved commit id\");\n        // ...\n\n// after - pair each resolution with its argument once, structurally\nfor (source_commit, unresolved_source) in resolved_commits.iter().zip(args) {\n    if source_commit.commit_id == target_commit.commit_id {\n        // error using unresolved_source directly\n    }\n}","handlingStrategy":"validation","validationCode":"// Construction-side invariant: resolutions and args are built together\nlet resolved: Vec<_> = args.iter().map(|arg| resolve_commit(arg).map(|c| (c, arg.clone()))).collect::<Result<_>>()?;\n// pairs are now structural; indexing cannot desync","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["rust","panic","internal-invariant","move","cli","argument-pairing"],"backgroundTag":"internal-invariant-panic","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}