fix(ci): the Alembic head watch evaluates through an unrelated conflict, and says so when it cannot (#2828) - #2832
Conversation
…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
|
…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
|
merge-train: pushed to this branch — one commit, mechanical, per the review above.
Nothing else touched. |
Summary
On 2026-09-15
alembic-head-watchfired 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 werelearnings.mdappend 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-treewrites 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 undersrc/backend/(enterprise/backend/)?migrations/versions/is aconflict. Anything else is evaluated on the merged version line as before, with the unrelated paths carried onto the verdict asconflictsElsewhereand 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 anerrorcommit status (GitHub's "could not run" colour, neither the all-clearsuccessnor the this-PR-forksfailure) plus a sticky naming the file. #2029's rule is against a falsesuccess; 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:
origin/dev(has0062_execution_fan_out_task_id)69f31efc(learnings.mdconflict +0062_agent_sync_state…)unknown— no verdictfork— 2 heads,learnings.mdnamed as unrelated → publishesfailureorigin/dev26279283clean,conflictsElsewhere: []devediting0061_…pyconflictconflictnaming0061_execution_open_canvas.py→ publisheserrorChanges
.github/workflows/alembic-head-watch.yml— conflict arm scoped to the version-line paths;write_verdictcarriesconflictsElsewhere; the error arm no longer swallows an evaluable exit-1.scripts/ci/alembic-head-verdict.js—conflict→errorstatus + a file-naming sticky;clean/forkrender 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 onVERSION_LINESand falls through; the paths come from merge-tree's own section, not its prose).architecture.mdInvariant Feature/vector log retention #3,feature-flows.mdci: pre-merge Alembic head check is stale-by-construction when dev advances #2533 row,learnings.md.Not here, by scope
learnings.mdfragments assembled at release (the collision's own durable fix) — a repo-wide convention change, not this guard.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)pull_requestself-test arm runs on this PR (it edits the workflow + verdict script) and publishes nothing (dryRun)devtouchingversions/**should evaluate feat(telegram): a tagged group turn knows the group's recent conversation (abilityai/trinity-enterprise#600) #2728 (still forked at0058, 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