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
- Strip the outer marker lines and keep only the resolved content per hunk
- 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
- 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 splicing model output or user pastes, strip the outer <<<<<<< / ======= / >>>>>>> scaffold before submission
- If a '=======' heading is genuine content, indent or prefix the line so it is not marker-shaped
- Remember validation is atomic: a rejected attempt wrote nothing, so fixing and resubmitting is safe
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
- Conflict {} of "{}" was addressed more than once
- "{}" has {} conflict{}, but conflict {} was addressed
- Validation
- Re-merging the conflicting trees of commit {commit_id} yield
- Commit {commit_id} has no conflicted files to resolve
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/ce8bbc4724eecbb9.
Report an issue: GitHub.