Skip to content

guard: auto_merge reads the generated-path registry instead of a hard-coded copy - #758

Open
matt82198 wants to merge 1 commit into
mainfrom
guard/auto-merge-uses-registry
Open

matt82198 wants to merge 1 commit into
mainfrom
guard/auto-merge-uses-registry

Conversation

@matt82198

Copy link
Copy Markdown
Owner

Problem

tools/auto_merge.py:97 hard-coded ['tests/CLAUDE.md', 'tools/CLAUDE.md'] when
auto-resolving merge conflicts — a re-typed copy of tools/generated_paths.py, which is
the single registry of repo-generated files and exists precisely to prevent this
duplication. tools/merge_queue.py already imports the registry; auto_merge.py did not.

The copy was already drifting: PR #757 registers tools/INDEX.md as a generated path.
Once it lands, merge_queue would restore tools/INDEX.md but auto_merge would leave a
batch conflicting on it unresolved — silently, with no gate to notice.

Fix

fix_branch() imports generated_paths as a module and calls generated_paths() at
call time, so the tool tracks whatever the registry currently holds instead of freezing
a copy at import. This works whether or not #757 has landed — nothing in generated_paths.py
was touched.

Conflict resolution stays path-by-path and registry-bounded: still no git stash (shared
stash stack across worktrees) and no blanket checkout of the whole tree; an unregistered
conflicted file still aborts the merge rather than being discarded.

TDD

Both guards were written first and fail on the pre-fix code (3 red), then pass:

  • Behavioural — a sentinel path is injected into the registry at runtime and
    fix_branch() must act on it (resolving --theirs for the sentinel). A hard-coded copy
    cannot see the sentinel, so this fails on the duplicated list and passes only on a live
    read. A second case shrinks the registry to empty and asserts nothing is resolved.
  • Structural (the durable guard) — an AST source scan over tools/ failing any module
    that re-types the registry as a literal collection. This is the part that stops the same
    class of duplicate returning the next time someone needs the list. It carries its own
    falsifiability tests: a planted copy IS caught, and a broader overlapping list (e.g.
    merge_telemetry.check_contended_files, which adds README.md/RELEASE-NOTES.md and
    means something different) is NOT a false positive.
$ python -m pytest tests/test_auto_merge_registry.py -q   # before the fix
3 failed, 3 passed
$ python -m pytest tests/test_auto_merge_registry.py tests/test_auto_merge_shell.py -q
12 passed
$ python tools/ci_shard_runner.py 0 1
Ran 4577 tests in 572.691s
OK (skipped=19)

Gates green: secret_scan --staged, import_resolution_check, verify_test_suite_count --check,
encoding_lint --check, subprocess_guard --check --baseline, claudemd_lint --root .,
claudemd_sync_gate --check, gen_tool_index --check, verify_test_coverage --check.

Scope

tools/auto_merge.py (+ its INDEX: docstring line and the regenerated tools/INDEX.md),
its new test, and the suite-count line regenerated by verify_test_suite_count --regenerate.
tools/generated_paths.py, tools/merge_queue.py and tools/commit_lint.py are untouched.

Noted, not fixed here (out of lane)

  • tools/import_resolution_check.py cannot resolve sibling imports inside tools/
    (no __init__.py, and the sys.path.insert(script_dir) pattern isn't modelled). Staging
    an unmodified tools/auto_merge.py on pristine origin/main makes the gate report
    from subprocess_common -> not found. It happens not to bite at push time because the
    gate reads git diff --cached, which is empty once work is committed — so the guardrail
    is effectively inert as a pre-push gate. Worth its own lane.
  • tools/commit_lint.py --range raises
    run_subprocess() got an unexpected keyword argument 'capture_output' (PR guard: fix commit_lint --range crash (caller migrated to current run_subprocess signature) #720's file).

🤖 Generated with Claude Code

…-coded copy

tools/auto_merge.py hard-coded ["tests/CLAUDE.md", "tools/CLAUDE.md"] when
resolving merge conflicts -- a re-typed copy of tools/generated_paths.py,
the exact duplication that registry exists to prevent. PR #757 adds
tools/INDEX.md to the registry; auto_merge would never have known.

fix_branch() now imports generated_paths as a MODULE and calls
generated_paths() at call time, so the tool tracks whatever the registry
currently holds rather than freezing a copy at import.

tests/test_auto_merge_registry.py adds two guards:
  - behavioural: injects a sentinel entry into the registry at runtime and
    asserts fix_branch resolves it (a copy cannot see the sentinel);
    shrinking the registry to empty resolves nothing
  - structural (durable): AST source scan over tools/ failing any module
    that re-types the registry as a literal collection, with its own
    falsifiability tests (planted copy is caught; a broader overlapping
    list such as merge_telemetry contended-files is not a false positive)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@matt82198 matt82198 added the merge-queue Queued for the merge-queue advancer daemon label Aug 3, 2026

This branch has not been deployed

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

Labels

merge-queue Queued for the merge-queue advancer daemon

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant