Skip to content

bug(repo): a committed conflict block sits in a tracked file on main, and no gate rejects conflict markers #17297

Closed

Description

@mrveiss

Problem

docs/.obsidian/workspace.json is tracked and contains a committed merge-conflict block on
origin/main. The file is not valid JSON, so anything that parses it fails.

Evidence

$ git show origin/main:docs/.obsidian/workspace.json | grep -nE '^(<<<<<<<|=======|>>>>>>>)'
182:<<<<<<< Updated upstream
210:=======
238:>>>>>>> Stashed changes

Verified at origin/main = aec5e09cfbbf0ff1101adbe72828f362598b4b67. Found by the end-of-session
conflict-marker gate while working an unrelated docs branch; my own commit touched only
docs/research/_index.md and one new research file.

Two things this says

  1. The markers are Updated upstream / Stashed changes — this came from a git stash pop
    conflict that was staged and committed without resolution, not from a merge or rebase. The git
    stash stack is shared across every worktree in this repo, which is precisely the hazard that
    makes a blind pop dangerous.
  2. No gate caught it. Sessions are told to grep '^<<<<<<< ' before a final push, but that is
    a convention, not an enforced check — so it only fires when someone remembers. A committed
    conflict marker reaching the default branch is the evidence that it did not fire here.

Acceptance criteria

  • docs/.obsidian/workspace.json is valid JSON on main — resolved to the intended content,
    not merely to whichever side parses.
  • Decide whether this file belongs in version control at all: it is Obsidian per-user editor
    state (open panes, cursor positions), which is per-developer and conflicts by nature. If it
    is not needed, .gitignore it and remove it — that removes the conflict class permanently.
  • A pre-commit or pre-push hook rejects ^<<<<<<< , ^======= and ^>>>>>>> in any staged
    or pushed file, so the convention becomes a gate.
  • A one-off sweep confirms no other tracked file on main carries conflict markers, and the
    sweep output is posted — a nil result stated as a nil result, not as silence.

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    👋 Auto-triage could not confidently classify this issue (no confident signal). Could a maintainer add the appropriate labels (frontend, backend, infrastructure, docs, testing and good-first-issue, intermediate, advanced)?

  2. mrveiss commented on Sep 24, 2026

    @mrveiss
    OwnerAuthor

    AC verification against merged main at fbe46dbaf3

    PR #17407 merged as fbe46dbaf3 with Closes #17297. Evidence read from merged code, not the PR diff.

    The marker is gone from the tree ✅

    git grep -l '^<<<<<<< ' origin/main    ->  0 files
    

    Before the merge there was exactly one: docs/.obsidian/workspace.json:182, <<<<<<< Updated upstream, tracked since 2026-04-03. That file is untracked now, and .gitignore:509 already matched it — the rule was there and inert, because .gitignore does not apply to a tracked path. So it cannot come back.

    The hook is armed ✅

    git show origin/main:.pre-commit-config.yaml

    171:  - id: check-merge-conflict
    179:    args: [--assume-in-merge]
    

    This is the substance of the issue. check-merge-conflict returns 0 unless the repo is mid-merge — it checks for MERGE_MSG plus MERGE_HEAD/rebase-* and exits before reading a single line otherwise. A git stash pop conflict creates none of those, so the markers it leaves were invisible to a hook that reported "Passed" on every commit for five months.

    A guard exists and cannot pass by not looking ✅

    test_the_sweep_actually_reaches_the_tree
    test_no_tracked_file_carries_a_conflict_marker
    test_the_hook_is_still_armed
    test_the_detector_recognises_each_marker_shape   (parametrised per marker)
    

    SCANNED_FILES carries a population floor, so "no markers found" is distinguishable from "no files read" — which is the failure this issue is an instance of, and the reason the original hook was useless rather than merely weak.

    Recorded because a tick should not imply more than it covers

    test_the_detector_recognises_each_marker_shape did not bind when first written. Its docstring said it asserted through _conflict_sites' own predicate; the assertion re-implemented that predicate inline and never called it. Dropping the bare-======= clause from the real detector left all three parametrised cases passing — including the separator case the docstring named as the easy miss.

    Repaired before merge: it now git-inits a throwaway repo under tmp_path, writes a file carrying the marker, stages it so git ls-files reports it, and asserts _conflict_sites returns that site. Mutation-proved in both directions — dropping the separator predicate fails 1 case, dropping the outer-prefix predicate fails 2, each caught by its own parametrisation.

    Worth keeping: the file said out loud that it was not re-implementing the predicate, and then did. A docstring claiming a test binds is not evidence that it does.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions