Resolve conflicts

When a commit conflicts, what the markers and the structured error look like, how to resolve them, and why a lost race is not a conflict.

A conflict is the one case slivingdoc refuses to decide for you: your changes and the accepted remote state changed the same lines of the same file. The operation returns CONTENT_CONFLICT, rewrites the visible directory with the merged result, and leaves conflict markers in the affected files. Nothing is lost, and nothing is accepted.

Two writers change the same lines; the commit returns conflict markers in the file for the caller to resolve

What you get back

The affected files contain standard markers:

<<<<<<< local
the caller's text
=======
the accepted remote text
>>>>>>> remote

A complete marker block is these exact lines, at column zero and in this order. The scanner accepts LF and CRLF line endings, finds every complete, non-nested block in a file, and counts rows one-based and inclusive. Changing one character in any of the three signature lines makes that block ordinary text again.

The CLI reports it like any other domain error:

CONTENT_CONFLICT · MERGE_CONFLICT
Resolve the conflict blocks before notes_commit.
  notes/today.md  conflict  lines 12-18, 40-42
next: edit the files, then commit
retryable: false

An agent decodes the same facts as structured data:

{
  "code": "CONTENT_CONFLICT",
  "reason": "MERGE_CONFLICT",
  "action": "EDIT_FILES",
  "files": [
    {
      "path": "notes/today.md",
      "reason": "TEXT_CONFLICT",
      "ranges": [{ "start": 12, "end": 18 }]
    }
  ]
}

The two reasons at the top level are MERGE_CONFLICT, for a conflict found by the three-tree merge, and UNRESOLVED_MARKERS, for a complete marker block that was already in the visible directory before any merge ran. Both carry the action EDIT_FILES. Each file entry names its own reason:

File reason Means
TEXT_CONFLICT A text conflict, with the marker ranges to resolve.
PATH_CONFLICT A file-versus-directory conflict. The ranges array is always empty.
UNRESOLVED_MARKERS A marker block that was already in the directory when the commit ran.

A file-versus-directory conflict works differently, because markers cannot describe it. The PATH_CONFLICT names the path, and the visible directory keeps your side: your file stays where the remote has a directory, and your directory’s files stay where the remote has a file. The remote side is left out of the directory and exists only in the accepted remote state. A text conflict below a path where you kept a file is dropped, and only the PATH_CONFLICT is reported. Since your directory now matches what you meant, committing it as it stands publishes your side in place of the remote one.

The very first pull into a directory cannot conflict at all: it is only allowed when the directory’s files are identical to the notebook’s (see Share a directory with humans).

Resolve it

Resolve the files with ordinary file tools. No Git command is involved, and there is nothing to abort or continue.

  1. Open each file the error named, at the line ranges it gave.
  2. Keep the text you want and delete the three marker lines.
  3. Commit again.
export SLIVINGDOC_BUCKET=my-notes
# edit notes/today.md, remove the marker lines
slivingdoc commit notes -m "merge today's notes"

The resolved directory becomes your local intent. If the remote state moved again while you were editing, the server merges your directory against the new state, and you may be handed another conflict to resolve — on the newer text.

Warning: A complete marker block is never accepted into the notebook, even one you wrote by hand and meant to keep. A commit that still contains one is refused with the reason UNRESOLVED_MARKERS before anything is published. Documenting the marker syntax in a note therefore needs one character changed in a signature line.

A lost race is not a conflict

Two writers publish at once; the loser merges against the new head and retries, and only overlapping lines become a conflict

Two writers that publish at the same moment both observed the same manifest, and the object store accepts only one replacement for it. The loser gets a precondition failure. That is expected contention, not a storage failure and not a conflict: the losing writer downloads what it missed, merges against the new head, waits a bounded randomized backoff, and retries by itself. You see nothing.

It only becomes a CONTENT_CONFLICT when the merge finds that both writers changed the same lines. Retries have fixed, configurable bounds — see --commit-retries in Configuration. Exhausting them returns an error and keeps your files available for another attempt.

Note: A file under a configured read-only path never carries a TEXT_CONFLICT or a PATH_CONFLICT: pull always takes the remote’s side there, so the merge sees identical content and reports nothing. A marker block found there by a commit is still refused as UNRESOLVED_MARKERS.

Next

Last updated September 29, 2026

Type to search the documentation.