{"record":{"id":"80e2bf5cbceaae84","repo":"gitbutlerapp/gitbutler","slug":"s-is-ambiguous-it-matches-more-than-one-uncommitted-change","errorCode":null,"errorMessage":"'{s}' is ambiguous - it matches more than one uncommitted change. Use more characters to disambiguate.","messagePattern":"'(.+?)' is ambiguous - it matches more than one uncommitted change\\. Use more characters to disambiguate\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/absorb.rs","lineNumber":66,"sourceCode":"            // so later commits cannot shadow them; branch selectors resolve in\n            // the full namespace. A selector that names neither is an error —\n            // silently falling back to absorbing everything is never intended.\n            let resolved =\n                match crate::id::parser::resolve_uncommitted_part(ctx, &id_map, s) {\n                    Ok(ids) if !ids.is_empty() => ids,\n                    _ => parse_sources(ctx, &id_map, s)?,\n                };\n            let mut acceptable = resolved.into_iter().filter(|id| {\n                matches!(id, CliId::UncommittedHunkOrFile { .. })\n                    || matches!(id, CliId::Branch(..))\n            });\n            let first = acceptable.next().ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"'{s}' does not name an uncommitted change or branch; refusing to absorb everything\"\n                )\n            })?;\n            if acceptable.next().is_some() {\n                anyhow::bail!(\n                    \"'{s}' is ambiguous - it matches more than one uncommitted change. Use more characters to disambiguate.\"\n                );\n            }\n            Ok(first)\n        })\n        .transpose()?;\n\n    let target = if let Some(source) = source {\n        match source {\n            CliId::UncommittedHunkOrFile(UncommittedHunkOrFile { hunks, .. }) => {\n                // Absorb this particular file\n                AbsorptionTarget::Hunks {\n                    hunks: hunks.map(|id_and_hunk| id_and_hunk.hunk).into(),\n                }\n            }\n            CliId::Branch(branch) => {\n                // Absorb everything that is assigned to this lane\n                AbsorptionTarget::Branch {","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/legacy/absorb.rs#L48-L84","documentation":"In `but absorb`, each positional source argument is matched against the set of uncommitted changes/branches. If the argument matches exactly one item it is used; if it matches two or more, the CLI refuses to guess and bails, telling you to add characters to disambiguate. A related guard bails when nothing matches at all.","triggerScenarios":"Running `but absorb <s>` where s is a prefix that uniquely-match-checks against more than one uncommitted hunk/file/branch name — e.g. `but absorb src` when both src/a.ts and src/b.ts are uncommitted.","commonSituations":"Using short prefixes for file paths in large diffs; two similarly named branches or files changed at once; muscle-memory abbreviations breaking after new files appear in the working tree.","solutions":["Extend the argument with more characters until it matches exactly one change (e.g. src/a instead of src)","Use the full file path or exact branch name","Run `but status` (or the absorb listing) to see candidate names and pick a unique one"],"exampleFix":"// before\nbut absorb src        # matches src/a.ts and src/b.ts\n// after\nbut absorb src/a.ts","handlingStrategy":"validation","validationCode":"function assertUniquePrefix(target, candidates) {\n  const hits = candidates.filter(c => c.startsWith(target));\n  if (hits.length > 1) throw new Error(`'${target}' is ambiguous; matches: ${hits.join(', ')}`);\n  if (hits.length === 0) throw new Error(`'${target}' matches no uncommitted change`);\n  return hits[0];\n}","typeGuard":"const isAmbiguous = (hits) => Array.isArray(hits) && hits.length > 1;","tryCatchPattern":"try {\n  await exec(`but absorb ${arg}`);\n} catch (e) {\n  if (e.message.includes('is ambiguous')) {\n    console.error('Lengthen the prefix or use the full file/branch name');\n  } else throw e;\n}","preventionTips":["Use full file paths or exact branch names in scripts instead of prefixes","Check `but status` before absorbing in trees with many similarly named changes","Re-verify prefixes after new files appear in the working tree"],"tags":["cli","ambiguity","absorb"],"backgroundTag":"ambiguous-argument-match","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}