Skip to content

fix(changelog): strike the committed conflict markers, and fail on merge residue anywhere (#1491) - #1492

Merged
github-actions[bot] merged 4 commits into
mainfrom
fix/issue-1491-conflict-markers
Aug 11, 2026
Merged

fix(changelog): strike the committed conflict markers, and fail on merge residue anywhere (#1491)#1492
github-actions[bot] merged 4 commits into
mainfrom
fix/issue-1491-conflict-markers

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes #1491

What was wrong

CHANGELOG/v4.md on main carried three committed conflict markers inside
[Unreleased], left by 1622e02a:

64:<<<<<<< HEAD
66:=======
68:>>>>>>> be03af9f (docs(changelog): 788 did not re-derive, and "every figure" was not every)

Both sides were distinct entries — #1442's stop-prompt bounds (line 65) and
#1371's noise filter (line 67) — and each appears exactly once in the file, so
the conflict was purely additive. Nothing was lost. Both entries are kept
verbatim; only the three marker lines go.

Why nothing caught it

The suite was fully green with the markers on main. Nothing in it opens a
tracked file to read its shape. The two checks that do read the changelog ask
other questions: release-docs-check asks whether [Unreleased] was drained at
release time, check_changelog_dupes.py asks whether two entries restate each
other. Merge residue is neither.

The guard

scripts/check_conflict_markers.py, run by pytest and by release-docs-check
(the latter so the failure lands as a file annotation on the offending line
rather than as a test failure the author has to locate).

  • Repo-wide, not changelog-shaped. The same accident put markers into
    docs/design/write-log-as-truth.md during the feat(detectors): pin and version the thresholds behind the 2.8% non-spine edges #1362/fix(spine): report both directions of the divergence and correct its denominator (#1356) #1378 rebase. A check
    scoped to whichever file was hit last is a check for the previous incident.
  • The separator is conditional, and had to be. Seven < or > followed by
    a space open no legitimate line in this repo. A line of seven = is a Setext
    heading underline in Markdown and a section rule in reStructuredText, and both
    occur under docs/. So a separator counts only in a file that already carries
    an open, close or diff3 base marker — the only shape a real conflict leaves.
    An unconditional rule would make the check unrunnable, and an unrunnable check
    gets switched off rather than fixed.
  • No exclusion list. Marker literals are built from repeated characters, so
    the script and its test do not trip the check they implement. Every path on an
    exclusion list is a path that can carry markers to main unseen.

Mutation checks

mutation result
restore the three markers on CHANGELOG/v4.md 3 tests red; script exits 1 naming all three lines
make the separator unconditional test_a_setext_underline_alone_is_not_a_conflict red
drop the close marker from the unambiguous set 4 tests red
count undecodable files as scanned test_scan_skips_binary_without_counting_it red

The live test asserts the scan read more than 100 files. A git ls-files that
returned nothing would otherwise pass having read none — which is how this class
of check stops working quietly.

Out of scope

The CHANGELOG/unreleased/ restructure (#1475, PR #1487), which removes the
conflict class itself rather than guarding its residue.

Summary by Sourcery

Guard against committed merge-conflict markers across the repository and document the fix in the changelog.

Bug Fixes:

  • Remove previously committed merge-conflict markers from CHANGELOG/v4.md and record the incident and repair in the changelog.

Enhancements:

  • Add a repo-wide check_conflict_markers.py script that scans tracked text files for merge-conflict residue and fails on findings.
  • Integrate the merge-conflict marker check into tests and the staging CI gate, ensuring non-vacuous scans and clear file/line annotations on failure.

Tests:

  • Introduce unit and live-repo tests for the merge-conflict marker scanner, covering marker detection, separator handling, binary file skipping, and non-vacuity of the scan.

Summary by CodeRabbit

  • New Features
    • Added automated checks to identify unresolved merge-conflict markers in repository files.
    • Reports detected issues with specific file and line locations.
  • Bug Fixes
    • Cleaned up changelog formatting by removing leftover conflict markers while retaining all entries.
  • Tests
    • Added coverage for standard and diff3 conflict markers, binary files, line reporting, and clean repository scans.

1622e02 left `<<<<<<< HEAD`, `=======` and `>>>>>>> be03af9` in the
[Unreleased] block. Both sides are distinct entries — #1442's stop-prompt
bounds and #1371's noise filter — and each appears exactly once in the
file, so the conflict was purely additive. Both are kept verbatim; only
the three marker lines go.

Refs #1491
Nothing read a tracked file for shape, so three marker lines sat in
CHANGELOG/v4.md through a fully green suite. The guard is repo-wide, not
changelog-scoped: the same accident put markers in a design doc during
the #1362/#1378 rebase, and a check scoped to the file hit last catches
only the previous incident.

The separator is conditional. Seven '<' or '>' plus a space open no
legitimate line here, but a line of seven '=' is a Setext underline in
Markdown and a section rule in reStructuredText, both of which occur in
docs/ — so it counts only in a file that already carries an open, close
or diff3 base marker. Marker literals are built from repeated characters
so the guard and its test do not trip themselves; an exclusion list was
the alternative and every entry on one is a file that can carry markers
to main unseen.

Mutation-checked: restoring the markers on v4.md turns three tests red
and the script exits 1 naming all three lines; making the separator
unconditional reddens the Setext control; dropping the close marker
reddens four; counting undecodable files as scanned reddens the
non-vacuity arm. The live test asserts the scan read >100 files, because
an ls-files that returned nothing would otherwise pass having read none.

Closes #1491
@robotrocketscience robotrocketscience added the author-Setr PR coordination mutex label Aug 11, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@robotrocketscience, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 487ab655-9364-4e56-91f9-4110459eabad

📥 Commits

Reviewing files that changed from the base of the PR and between 502a59a and c8ebb35.

📒 Files selected for processing (2)
  • scripts/check_conflict_markers.py
  • tests/test_conflict_markers_1491.py
📝 Walkthrough

Walkthrough

The pull request removes committed conflict markers from CHANGELOG/v4.md, adds a repository-wide scanner, tests its detection behavior, and runs it for every pull request through the staging gate.

Changes

Conflict marker detection

Layer / File(s) Summary
Resolve changelog markers
CHANGELOG/v4.md
Removes conflict markers and preserves both changelog entries as separate bullets.
Implement marker scanner
scripts/check_conflict_markers.py
Adds tracked-file discovery, UTF-8 scanning, marker detection, GitHub-style diagnostics, and distinct exit statuses.
Validate and run the gate
tests/test_conflict_markers_1491.py, .github/workflows/staging-gate.yml
Tests marker layouts, separator rules, binary-file handling, locations, and live scans. Runs the scanner for every pull request.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels: attn:review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the changelog repair and repository-wide conflict-marker guard.
Description check ✅ Passed The description explains the problem, implementation, verification, linked issue, and explicit out-of-scope work.
Linked Issues check ✅ Passed The changes satisfy issue #1491 by removing the markers, adding repository-wide detection, and covering mutation and non-vacuous scan tests.
Out of Scope Changes check ✅ Passed The changes remain within issue #1491 scope and do not include the explicitly excluded changelog restructure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-1491-conflict-markers

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a repo-wide merge-conflict marker guard that runs in CI and pytest, removes committed markers from CHANGELOG/v4.md, and documents the fix and guard behaviour in the changelog.

Sequence diagram for repo-wide merge-conflict marker guard

sequenceDiagram
    actor Developer
    participant Pytest
    participant CI_workflow as CI_workflow_staging_gate
    participant Script as check_conflict_markers_py
    participant Git as git

    Developer->>Pytest: run tests
    Pytest->>Script: main(argv)
    CI_workflow->>Script: main(argv)
    Script->>Git: tracked_files(repo)
    Git-->>Script: list of tracked files
    Script->>Script: scan(paths)
    Script->>Script: find_markers(text)
    Script-->>Pytest: exit 1/2/0 with stderr annotations
    Script-->>CI_workflow: exit 1/2/0 with stderr annotations
Loading

Flow diagram for conflict marker detection with conditional separator

flowchart TD
    A[read_text utf-8] --> B[split into lines]
    B --> C{line startswith OPEN_MARKER or CLOSE_MARKER<br/>or line rstrip == BASE_MARKER}
    C -->|any match| D[collect unambiguous markers]
    C -->|no match| E[return empty list]
    D --> F{line rstrip == SEPARATOR}
    F -->|collect| G[add separator lines]
    F -->|none| H[keep only unambiguous markers]
    G --> I[sort markers and return]
    H --> I[sort markers and return]
Loading

File-Level Changes

Change Details Files
Introduce a repo-wide script and tests to detect committed merge-conflict markers and fail CI when any are present.
  • Add scripts/check_conflict_markers.py that scans tracked files, treating merge markers as findings and skipping binary files while ensuring non-vacuous scans.
  • Implement marker detection logic that treats OPEN/CLOSE/BASE markers as unambiguous and only reports the separator when a real conflict shape is present.
  • Provide a CLI-style main() that prints GitHub Actions ::error annotations for offending lines, reports counts, and exits with distinct codes for success, findings, or zero readable files.
  • Add tests that cover conflict shapes (including diff3 style), Setext underline non-conflicts, conditional separator handling, binary-skip behaviour, file/line reporting, and a live scan over tracked files asserting both emptiness and non-vacuity.
scripts/check_conflict_markers.py
tests/test_conflict_markers_1491.py
Wire the conflict-marker guard into the staging-gate GitHub Actions workflow so it runs on every PR.
  • Extend the staging-gate workflow job to run the new scripts/check_conflict_markers.py check after the changelog-duplicates check.
  • Document in workflow comments that this guard is repo-wide, runs for every PR, and complements the pytest invocation by producing file-level annotations.
.github/workflows/staging-gate.yml
Clean up the v4 changelog by removing committed conflict markers and add an entry describing the incident and the new guard.
  • Remove the leftover conflict marker block around two changelog entries, keeping both entries verbatim without markers.
  • Add a detailed Fixed entry to CHANGELOG/v4.md describing the committed markers incident, why previous checks missed it, how the new guard works (repo-wide, conditional separator, no exclusion list), and how it is mutation-tested.
CHANGELOG/v4.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1491 Remove the three committed merge-conflict marker lines from CHANGELOG/v4.md while preserving both entries verbatim, each exactly once.
#1491 Introduce a guard/test that fails on any merge-conflict marker (<<<<<<<, >>>>>>> and conflict ===== separator) in any tracked text file in the repository, not just the changelog.
#1491 Ensure the new guard is mutation-checked: tests that go red when markers are reintroduced in fixtures, and assert that the guard scans a non-zero (non-trivial) number of files so passes are not vacuous.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 401 changed lines (limit: 200)
  • 4 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check_conflict_markers.py`:
- Around line 54-59: Update the conflict-marker scan comprehension in
scripts/check_conflict_markers.py:54-59 to recognize BASE_MARKER when it appears
at the end of a line or is followed by a space and label, while preserving
existing marker detection. Update the test fixture in
tests/test_conflict_markers_1491.py:60-64 to use a labeled diff3 base marker and
retain the expected four reported lines.
- Around line 88-94: The scan loop in scripts/check_conflict_markers.py (the
path-reading logic around find_markers) must read symlink payloads without
following links, while treating other read failures as scan failures rather than
silently skipping them; update tests/test_conflict_markers_1491.py at lines
91-101 to create a broken symlink with an open-marker payload and assert that
the scan reports it.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 19ac0c81-0b58-49df-a52f-37051605e843

📥 Commits

Reviewing files that changed from the base of the PR and between 2409b7e and 502a59a.

📒 Files selected for processing (4)
  • .github/workflows/staging-gate.yml
  • CHANGELOG/v4.md
  • scripts/check_conflict_markers.py
  • tests/test_conflict_markers_1491.py

Comment thread scripts/check_conflict_markers.py
Comment thread scripts/check_conflict_markers.py
…lowed

Two review findings on the guard itself.

The ancestor marker only matched a bare seven pipes, but a real merge
writes it labelled — '||||||| merged common ancestors' from a merge,
'||||||| <ref>' from a --conflict=diff3 checkout — so the only form the
rule caught was the one this repo could not acquire. It now accepts
bare or labelled, and moves from the unambiguous set to the conditional
one beside the separator: seven pipes is also a Markdown table row of
six empty cells. That gives up nothing real, because git writes all of
a conflict's markers or none.

scan() followed symlinks and swallowed every OSError. Following meant
scanning whatever the link pointed at, possibly outside the repo and
tracked by nobody, and reporting it against a path whose committed blob
is a target string; a dangling link raised and was silently skipped. A
symlink is now read with readlink, which is the content git stores, and
any other read failure raises UnreadableFile — main turns that into
exit 2, because a skip is indistinguishable from a clean file in the
summary and would shrink coverage while still reporting success.

Mutation-checked: dropping the condition reddens the table and Setext
controls; matching only the bare ancestor marker reddens both labelled
cases; following symlinks reddens both link tests; restoring the silent
OSError skip reddens the unreadable-file arm.

Refs #1491
@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 11, 2026
@github-actions
github-actions Bot merged commit c8ebb35 into main Aug 11, 2026
30 checks passed
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Aug 11, 2026
@github-actions

Copy link
Copy Markdown

merge-train: merged c8ebb35main via FF push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author-Setr PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

changelog: git conflict markers are committed on main, and nothing in the suite reads for them

1 participant