Skip to content

fix(ci): the Alembic head watch evaluates through an unrelated conflict, and says so when it cannot (#2828) - #2832

Merged
vybe merged 2 commits into
devfrom
fix/2828-alembic-watch-conflict-tolerant
Sep 16, 2026
Merged

vybe merged 2 commits into
devfrom
fix/2828-alembic-watch-conflict-tolerant

Conversation

@dolho

@dolho dolho commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Summary

On 2026-09-15 alembic-head-watch fired correctly at 14:58 and answered "conflicts with dev — head check not evaluated" for four of nine open PRs — two of which carried a live two-heads fork. All four conflicts were learnings.md append collisions. A watcher that stops on any conflict is absent on exactly the days a fork is likeliest, and its "not evaluated" was a comment with no status, which reads as nothing on a checks list. Both forks reached the train green and were caught by hand.

Two changes, both of the issue's suggested directions 1 and 2:

1. The conflict arm asks where. git merge-tree --write-tree writes the merged tree on exit 1 too — conflicted files carry markers, every other path is the real three-way merge — and lists the conflicted paths on its own stdout (the <mode> <oid> <stage>\t<path> section). The guard reads two directories, so only a conflict under src/backend/(enterprise/backend/)?migrations/versions/ is a conflict. Anything else is evaluated on the merged version line as before, with the unrelated paths carried onto the verdict as conflictsElsewhere and named on the status description + sticky (capped at 20, fenced — they are author-controlled).

2. "Could not evaluate" is a visible state. conflict — now only a revision file edited on both sides — publishes an error commit status (GitHub's "could not run" colour, neither the all-clear success nor the this-PR-forks failure) plus a sticky naming the file. #2029's rule is against a false success; it never argued for silence.

Proof — against the real case, not the YAML

The evaluate step's bash was extracted verbatim from the workflow and run locally with the fetch swapped for a pinned ref:

base PR head before after
origin/dev (has 0062_execution_fan_out_task_id) #2805 pre-fix 69f31efc (learnings.md conflict + 0062_agent_sync_state…) unknown — no verdict fork — 2 heads, learnings.md named as unrelated → publishes failure
origin/dev #2805 fixed 26279283 clean, conflictsElsewhere: []
synthetic dev editing 0061_…py synthetic PR editing the same file conflict conflict naming 0061_execution_open_canvas.py → publishes error

Changes

  • .github/workflows/alembic-head-watch.yml — conflict arm scoped to the version-line paths; write_verdict carries conflictsElsewhere; the error arm no longer swallows an evaluable exit-1.
  • scripts/ci/alembic-head-verdict.jsconflicterror status + a file-naming sticky; clean/fork render the unrelated paths; header rule 1 rewritten with the reason.
  • tests/unit/test_2533_alembic_head_watch.py — the "a conflict publishes no status" pin is re-anchored (with the reason); two new executed verdict tests (version-line conflict → error; unrelated conflict does not change the verdict but is named, list capped); two new shape pins (the arm scopes on VERSION_LINES and falls through; the paths come from merge-tree's own section, not its prose).
  • architecture.md Invariant Feature/vector log retention #3, feature-flows.md ci: pre-merge Alembic head check is stale-by-construction when dev advances #2533 row, learnings.md.

Not here, by scope

  • Per-PR learnings.md fragments assembled at release (the collision's own durable fix) — a repo-wide convention change, not this guard.
  • A merge-time check (direction 3) — the only moment the real merged graph exists; a separate design.

Both named on the issue.

Test Plan

  • cd tests && pytest unit/test_2533_alembic_head_watch.py unit/test_2068_alembic_heads_guard.py -v — 94 passed, 2 skipped (node present locally)
  • The three simulations above, on the extracted step body
  • The workflow's own pull_request self-test arm runs on this PR (it edits the workflow + verdict script) and publishes nothing (dryRun)
  • After merge: the next push to dev touching versions/** should evaluate feat(telegram): a tagged group turn knows the group's recent conversation (abilityai/trinity-enterprise#600) #2728 (still forked at 0058, still conflicting elsewhere?) and post a real verdict where it previously posted "not evaluated"

Fixes #2828

🤖 Generated with Claude Code

https://claude.ai/code/session_01VpvcfgWkmQPD7DrDLmATTf

…ct, and says so when it cannot (#2828)

On 2026-09-15 the watch fired correctly and answered "conflicts with dev —
head check not evaluated" for four of nine open PRs, two of which carried
a live two-heads fork. All four conflicts were `learnings.md` append
collisions. A watcher that stops on ANY conflict is absent on exactly the
busy days a fork is likeliest; both forks reached the train green.

- The conflict arm now asks WHERE. `git merge-tree --write-tree` writes the
  merged tree on exit 1 too, and lists the conflicted paths on its own
  stdout; only a conflict under `src/backend/(enterprise/backend/)?migrations/
  versions/` is a `conflict`. Anything else is evaluated on the real
  three-way merge of the version directories, with the unrelated paths
  carried onto the verdict as `conflictsElsewhere`.
- `alembic-head-verdict.js`: `clean`/`fork` name the unrelated files on the
  status description and the sticky (capped at 20, fenced); `conflict` —
  now only a revision file edited on both sides — publishes a visible
  `error` status plus a sticky that names the file, instead of a comment
  alone. #2029's rule is against a false `success`; it never argued for
  silence, and silence is how two forks rode to the train.

Proven against the real case, not the YAML: the evaluate step extracted
and run locally against origin/dev + #2805's pre-fix head → `fork` with
`learnings.md` named (was `unknown`); the fixed head → `clean`; a
synthetic both-sides revision edit → `conflict`. Shape pins and executed
verdict tests updated in test_2533; the "a conflict publishes no status"
pin is re-anchored with the reason.

Not here, by scope: per-PR learnings fragments (the collision's own fix)
and a merge-time check (direction 3) — both named on the issue.

Fixes #2828

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpvcfgWkmQPD7DrDLmATTf
@dolho
dolho requested a review from vybe September 16, 2026 04:18
@dolho

dolho commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/validate-pr + /review + /cso --diff — head 4a37e27c3 vs dev (merge-train pre-validation, lane C)

Scope: CLEAN — issue directions 1 + 2 delivered; direction 1 CHANGED for the better (merge-tree's written tree + path-scoped refusal instead of grafting via git show); direction 3 and the learnings-fragment fix explicitly deferred on the issue.

Critical: none. Write scopes, triggers and permissions: untouched; error lands on an advisory context.

Informational

  • I1 (8/10) VERSION_LINES misses git's C-quoted paths. Reproduced on git 2.43: a conflict on versions/0003_é.py prints "src/backend/migrations/versions/0003_\303\251.py" (leading ", core.quotePath), the ^src/… anchor fails, the leg is classed elsewhere, and check_alembic_heads.py omits the unparseable marker-bearing file → PASSsuccess on a tree that was never evaluable. Low realism (needs a non-ASCII/"/\ revision filename on both sides), so not blocking. Mechanical: git -c core.quotePath=off merge-tree … (or --name-only) + ^"? in the anchor.
  • I2 (9/10) The guard && [ -z "$evaluable_conflict" ] at the unknown arm is the load-bearing line of the fix and no test asserts it — the new test slices the body before if [ "$rc" -ne 0 ]. Delete the guard → every unrelated-conflict leg falls into unknown again (bug(ci): an Alembic version-line fork passes every required check, and the backstop for it is defeated by a learnings.md conflict #2828 reinstated) with all 94 tests green. Mechanical: pin it in the existing text test.
  • I3 (8/10) The new bash branch has not executed under CI: the self-test run evaluated this PR, which is MERGEABLE, so merge-tree exited 0 and neither arm ran. Its only execution is the author's local extraction (body table).
  • I4 (7/10) Prose nit: error and failure render the same red X; only the description differs. The behavioural claim (neither success nor failure) holds.

/cso --diff: nothing above LOW. No ${{ github.event.* }} in run: (all via env:), no pull_request_target, only GITHUB_TOKEN. Untrusted conflicted paths → job log via sed 's/^/ /' (cannot form a ::workflow-command::) → jq --argfenced() with a longest-backtick-run fence; status descriptions carry a constant or a number. First-party actions tag-pinned (pre-existing).

Coverage: JS verdictFor is executed (node -e against the real module: conflict→error, elsewhere carried into description + body, 60-path cap). The workflow bash is text-matched only, and I2 is the one guard not even text-pinned.

Verdict: READY. I1 + I2 are mechanical and will be applied on this branch before the train (announced here when pushed). Fixes #2828 bare form ✓; 27/27 checks green.

…arm guard is pinned (#2832) — mechanical, per the merge-train note on the PR

- `git -c core.quotePath=off merge-tree …` plus `^"?` in VERSION_LINES: a
  revision path git C-quotes (non-ASCII, `"`, `\`, control byte) no longer
  slips past the anchor into the *elsewhere* class, where the guard would
  run over a marker-bearing file that check_alembic_heads.py omits as
  unparseable and read PASS.
- The `[ -z "$evaluable_conflict" ]` guard on the unknown arm — the one line
  the #2828 fix turns on — is now asserted; deleting it fails
  test_an_unrelated_conflict_no_longer_stops_the_evaluation (mutation-checked).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpvcfgWkmQPD7DrDLmATTf
@dolho

dolho commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

merge-train: pushed to this branch — one commit, mechanical, per the review above.

  • VERSION_LINES='^"?src/backend/…' + git -c core.quotePath=off merge-tree (I1): a C-quoted revision path can no longer be classed elsewhere and read PASS through the guard's unparseable-file omission.
  • test_2533 now pins evaluable_conflict=1 in the conflict arm and the [ -z "$evaluable_conflict" ] guard on the unknown arm (I2). Mutation: guard removed → test_an_unrelated_conflict_no_longer_stops_the_evaluation red. Suite: 94 passed, 2 skipped.

Nothing else touched.

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

merge-train: batch validated on train/20260916-0919 (train PR #2839)

@vybe
vybe merged commit 550122b into dev Sep 16, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants