gitbutlerapp/gitbutler · error

' ' is ambiguous - it matches more than one uncommitted…

Error message

'{s}' is ambiguous - it matches more than one uncommitted change. Use more characters to disambiguate.

What it means

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.

Solutions

  1. Extend the argument with more characters until it matches exactly one change (e.g. src/a instead of src)
  2. Use the full file path or exact branch name
  3. Run `but status` (or the absorb listing) to see candidate names and pick a unique one

Example fix

// before
but absorb src        # matches src/a.ts and src/b.ts
// after
but absorb src/a.ts
Defensive patterns

Strategy: validation

Validate before calling

function assertUniquePrefix(target, candidates) {
  const hits = candidates.filter(c => c.startsWith(target));
  if (hits.length > 1) throw new Error(`'${target}' is ambiguous; matches: ${hits.join(', ')}`);
  if (hits.length === 0) throw new Error(`'${target}' matches no uncommitted change`);
  return hits[0];
}

Type guard

const isAmbiguous = (hits) => Array.isArray(hits) && hits.length > 1;

Try / catch

try {
  await exec(`but absorb ${arg}`);
} catch (e) {
  if (e.message.includes('is ambiguous')) {
    console.error('Lengthen the prefix or use the full file/branch name');
  } else throw e;
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/80e2bf5cbceaae84. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/command/legacy/absorb.rs:66

            // so later commits cannot shadow them; branch selectors resolve in
            // the full namespace. A selector that names neither is an error —
            // silently falling back to absorbing everything is never intended.
            let resolved =
                match crate::id::parser::resolve_uncommitted_part(ctx, &id_map, s) {
                    Ok(ids) if !ids.is_empty() => ids,
                    _ => parse_sources(ctx, &id_map, s)?,
                };
            let mut acceptable = resolved.into_iter().filter(|id| {
                matches!(id, CliId::UncommittedHunkOrFile { .. })
                    || matches!(id, CliId::Branch(..))
            });
            let first = acceptable.next().ok_or_else(|| {
                anyhow::anyhow!(
                    "'{s}' does not name an uncommitted change or branch; refusing to absorb everything"
                )
            })?;
            if acceptable.next().is_some() {
                anyhow::bail!(
                    "'{s}' is ambiguous - it matches more than one uncommitted change. Use more characters to disambiguate."
                );
            }
            Ok(first)
        })
        .transpose()?;

    let target = if let Some(source) = source {
        match source {
            CliId::UncommittedHunkOrFile(UncommittedHunkOrFile { hunks, .. }) => {
                // Absorb this particular file
                AbsorptionTarget::Hunks {
                    hunks: hunks.map(|id_and_hunk| id_and_hunk.hunk).into(),
                }
            }
            CliId::Branch(branch) => {
                // Absorb everything that is assigned to this lane
                AbsorptionTarget::Branch {

View on GitHub (pinned to 58e5313667)