gitbutlerapp/gitbutler · error · anyhow::Error

"{}" has {} conflict{}, but conflict {} was addressed

Error message

"{}" has {} conflict{}, but conflict {} was addressed

What it means

locate_hunk() resolves each spec's (path, 1-based hunk) address against the request. Hunks are numbered starting at 1, so hunk 0 is always invalid, and a number larger than the file's conflict count is stale. The message tells you the file's real hunk count so you can recompute. Nothing is written when this fails.

Source

Thrown at crates/but-api/src/resolve/apply.rs:120

            // Naming the reason matters here: the file *is* conflicted, it just
            // has no hunks to address, so "not a conflicted file" would read as
            // a caller mistake rather than a property of the conflict.
            let normalized = normalize_path(path);
            match request
                .manual
                .iter()
                .find(|file| normalize_path(&file.path) == normalized)
            {
                Some(file) => format!(
                    "\"{path}\" cannot be resolved this way: {} Resolve this commit in edit mode instead.",
                    file.reason
                ),
                None => format!("\"{path}\" is not a conflicted file of this commit"),
            }
        })?;
    let file = &request.files[file_index];
    if hunk == 0 || hunk > file.hunks.len() {
        bail!(
            "\"{}\" has {} conflict{}, but conflict {} was addressed",
            file.path,
            file.hunks.len(),
            if file.hunks.len() == 1 { "" } else { "s" },
            hunk
        );
    }
    Ok((file_index, hunk - 1))
}

/// Map normalized request paths to file indices, rejecting collisions.
pub(crate) fn index_files_by_path(
    request: &ResolutionRequest,
) -> anyhow::Result<BTreeMap<String, usize>> {
    let mut files_by_path = BTreeMap::new();
    for (index, file) in request.files.iter().enumerate() {
        if files_by_path
            .insert(normalize_path(&file.path), index)

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Re-fetch the conflict state with commit_conflicts(commit_id) and recompute spec hunk numbers from its fresh hunks list
  2. After any partial resolution, address follow-up specs to the new_commit id returned by that call
  3. Treat hunk indices as 1-based positions in the file's hunk list

Example fix

// before: reusing stale hunk numbers after a partial resolve
await api.resolveCommitConflictHunks(commitId, specs);

// after: re-sync against the current commit first
const conflicts = await api.commitConflicts(commitId);
const file = conflicts.files.find(f => normalizePath(f.path) === normalizePath(spec.path));
const safeSpecs = file
  ? specs.filter(s => s.path === file.path && s.hunk >= 1 && s.hunk <= file.hunks.length)
  : [];
await api.resolveCommitConflictHunks(commitId, safeSpecs);
Defensive patterns

Strategy: validation

Validate before calling

// Recompute against the live conflict list before resolving
const conflicts = await api.commitConflicts(commitId);
const file = conflicts.files.find(f => normalizePath(f.path) === normalizePath(spec.path));
const ok = file !== undefined && spec.hunk >= 1 && spec.hunk <= file.hunks.length;
if (!ok) throw new Error(`stale hunk ${spec.hunk} for ${spec.path}; refresh conflicts`);

Try / catch

try {
  await api.resolveCommitConflictHunks(commitId, specs);
} catch (err) {
  if (/but conflict \d+ was addressed/.test(String(err))) {
    const fresh = await api.commitConflicts(commitId);
    specs = remapSpecsToFreshHunks(specs, fresh); // rebuild indices, then retry once
    await api.resolveCommitConflictHunks(commitId, specs);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling resolve_commit_conflict_hunks with hunk numbers computed from a DIFFERENT view of the commit: a previous partial resolution rewrote the commit (new id, fewer hunks), the UI held an old conflict list, or the caller passed 0-based indices from some other diff/hunk API.

Common situations: Stale UI state after a partial resolve (must re-fetch against the returned new_commit); two concurrent resolutions racing; mixing 0-based indices from a git-diff style API with this 1-based API.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/2f9a5c05240e3948. Report an issue: GitHub.