gitbutlerapp/gitbutler · error · anyhow::Error

The resolution for "{path}" contains a conflict marker ({mar

Error message

The resolution for "{path}" contains a conflict marker ({marker:?})

What it means

ensure_no_markers() inspects every line of a HunkResolution::Content payload and rejects content containing a conflict-marker-shaped line (<<<<<<<, =======, >>>>>>> style, CRLF tolerated). Splicing marker-shaped content back into the ours/theirs trees would recreate a conflict inside the 'resolved' commit and corrupt the tree synthesis, so validation fails before anything is written.

Source

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

            .is_some()
        {
            bail!(
                "Two conflicted paths normalize to the same value ({:?}); resolve this commit manually instead",
                normalize_path(&file.path)
            );
        }
    }
    Ok(files_by_path)
}

/// Reject content that contains a conflict-marker-shaped line.
pub(crate) fn ensure_no_markers(content: &str, path: &str) -> anyhow::Result<()> {
    if let Some(marker) = content
        .lines()
        .map(|line| line.strip_suffix('\r').unwrap_or(line))
        .find(|line| is_marker_shaped(line))
    {
        bail!("The resolution for \"{path}\" contains a conflict marker ({marker:?})");
    }
    Ok(())
}

/// Normalize a path for comparison: trim, backslashes to slashes, strip a
/// leading `./`, collapse duplicate slashes. Applied to both request and
/// caller-provided paths, so callers matching against [`ConflictedFile`] paths
/// should normalize with this too.
///
/// [`ConflictedFile`]: super::ConflictedFile
pub fn normalize_path(path: &str) -> String {
    let mut normalized = path.trim().replace('\\', "/");
    while normalized.contains("//") {
        normalized = normalized.replace("//", "/");
    }
    normalized
        .strip_prefix("./")
        .map(str::to_owned)

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Strip the outer marker lines and keep only the resolved content per hunk
  2. If the marker-like line is genuine content (e.g. a '=======' heading), indent it or prefix it so the line no longer starts marker-shaped
  3. Re-submit; validation is atomic so the previous attempt wrote nothing

Example fix

// before: content still carries the conflict scaffold
"<<<<<<< ours\nkeep this\n=======\nand this\n>>>>>>> theirs"

// after: only the merged result
"keep this\nand this"
Defensive patterns

Strategy: validation

Validate before calling

// Reject marker-shaped lines client-side before submitting content specs
const MARKER_RE = /^(<{7}|={7}|>{7})/;
function isMarkerFree(content: string): boolean {
  return !content.split('\n').some(line => MARKER_RE.test(line.replace(/\r$/, '')));
}
const safeSpecs = specs.filter(s => s.resolution.type !== 'content' || isMarkerFree(s.resolution.content));

Type guard

function isResolvableContent(content: string): boolean {
  return isMarkerFree(content); // false => will be rejected by ensure_no_markers
}

Prevention

When it happens

Trigger: Submitting resolve_commit_conflict_hunks with resolution content that still contains the original conflict block - typically an AI/model output that echoed the markers, or a user pasting the whole conflicted file (markers included) as the resolution.

Common situations: LLM resolutions that copy the <<<<<<< ours ... ======= ... >>>>>>> scaffold verbatim; hand-pasted merges where only the inner half was meant; files that legitimately start a line with '=======' (README section underlines) getting caught as marker-shaped.

Related errors


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