Skip to content
This repository was archived by the owner on Aug 11, 2026. It is now read-only.

fix(ci): reject a PR whose merge into main would leave two Alembic heads - #556

Merged
Peyton-Spencer merged 2 commits into
mainfrom
peyton/migration-order-merge-result
Jul 28, 2026
Merged

fix(ci): reject a PR whose merge into main would leave two Alembic heads#556
Peyton-Spencer merged 2 commits into
mainfrom
peyton/migration-order-merge-result

Conversation

@Peyton-Spencer

@Peyton-Spencer Peyton-Spencer commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

main went red and undeployable on 2026-07-28 with two Alembic heads — 1183 DB
test errors and a dead Deploy job — and the Migration order job that exists to
prevent exactly this was green on both PRs the whole time. #555 fixed the
symptom. This closes the hole that let it through.

The job validated the PR branch in isolation. A branch that is perfectly
linear against the main it was cut from stays linear no matter what lands on
main afterwards, so the divergence existed only in the merge — the thing that
actually gets deployed. This resolves the merge result and asserts exactly
one head.

Why the existing check didn't fire

Not a logic bug. A freshness bug:

when what
2026-07-27 18:59 #505's last Migration order run — green, and correct
2026-07-27 20:24 #524 lands c7a4f1e2b903, 85 minutes later
2026-07-28 16:24 #505 merges on that same ~21-hour-old green check

GitHub re-runs a PR's checks when the PR moves, not when its base moves.
"Require branches to be up to date before merging" is off on this repo
(strict: false), so nothing forced a re-evaluation, and the stale green stayed
mergeable the entire time. A script-only fix would not have caught this
hence the second half below.

Changes

scripts/check_migration_order.py — resolves base + branch rather than the
branch. Migrations are immutable and none may be deleted (both asserted, now
against the merge base rather than the base tip), so the merge is exactly the
union of the two file sets and needs no actual merge to evaluate. That also lets
the check run for any PR from any checkout, which the sweep needs. Takes an
optional head_ref.

scripts/recheck_open_pr_migrations.sh (new) — runs on every push to main
and re-checks each open PR against the main that now exists, posting a
migration-order/merge-result commit status. This is what closes the window: a
PR that has just been made undeployable now says so on the PR, instead of
staying green until someone merges it. PRs that add no migration are reported
green without being fetched, so the context is present on every PR and a stale
red can't outlive the migration that caused it. If main itself is divergent the
sweep fails loudly and posts nothing — red statuses on innocent PRs are how a
guard gets muted.

Failures name both revisions, the file each lives in, which side each came from,
and both remedies:

migration-order: merging this branch into origin/main would leave 2 Alembic heads, not one:
    c7a4f1e2b903  alembic/versions/2026_07_27_reinstate_an_evicted_submission.py  (already a head on origin/main)
    f4b7d2c91ae5  alembic/versions/2026_07_27_add_never_disclose_release_policy.py  (added by this branch)
Alembic linears by down_revision, not by merge date, so two branches that each extend
the same parent stay divergent however git merges them. `alembic upgrade head` then
refuses to run with "Multiple head revisions are present for given argument 'head'",
which fails every migration -- the deploy and the whole DB test tier with it.
Reconcile on this branch, before merging, either way:
  * rebase onto current origin/main and repoint down_revision at its head c7a4f1e2b903
    (renumbering the YYYY_MM_DD_ filename too if it now precedes the newest migration there), or
  * uv run alembic merge -m "merge heads" c7a4f1e2b903 f4b7d2c91ae5
Review both branches for conflicting changes to the same table before assuming an
empty merge revision is correct.

A branch that merely inherits a divergent main is told it is not at fault.

CLAUDE.md — the "rebase onto current origin/main" advice is now
enforceable, so it says so. Also documents the stale-test-template gotcha found
during #555: a cached ditto_test_template built against a broken chain fails
every DB test long after the chain is fixed, in a way that reads as your change's
fault (513 failures on a clean worktree; green after make test-db-reset). The
tell is breadth — a real regression fails tests near what you touched.

It already found two live recurrences

Run against the open PRs as they stand today, both of which would re-break main
the moment they merge:

Both are green on the current check. Neither needs anything but a rebase.

Test plan

  • The exact 2026-07-28 fork rebuilt as two real branches in a scratch git
    repo — asserts the stale branch was genuinely green against the base it
    was cut from, and is rejected against main as it is now. The old
    script passes this input; the new one fails it.
  • Rebasing that branch onto current main clears the failure, i.e. the
    remedy the message recommends actually works.
  • A migration missing only because main moved is not misreported as a
    deletion (merge-base, not base-tip).
  • Sweep dry-run against all 22 open PRs with gh api stubbed: 20 green, feat(scores): record what a scored run spent on the language model #525
    and feat(attestation): self-serve cryptographic owner-link attestation #503 red.
  • make lint, make lint-copy, make typecheck clean; shellcheck clean.
  • make test2482 passed in deterministic order (-p no:randomly). A
    randomized-order run first showed 3 failures in test_inference.py /
    test_public.py (caplog assertions); all 3 pass in isolation and are
    order-dependent flakes, not this change — which adds nothing outside
    scripts/, .github/, CLAUDE.md and ditto/tests/scripts/.
  • Confirm the push-triggered job posts statuses once merged (it cannot run
    until it is on main).

One thing this PR cannot do

Migration order is not in this repo's required status checks — only
copy-lint and lint-and-test (3.12) are. So even when it goes red it does not
block a merge today. Branch protection can't be changed from a PR; worth adding
Migration order (and/or migration-order/merge-result) to the required
contexts, and considering strict: true, which would independently close the
staleness window.

🤖 Generated with Claude Code

The Migration order job validated the PR branch in isolation, so a branch
that was linear against the main it was cut from stayed green after a
second migration landed. That is the 2026-07-28 outage: #505's last check
ran 2026-07-27T18:59, #524 landed 85 minutes later, and #505 merged ~21
hours after that on a check that had been true when it ran and false ever
since. main ended up with two heads, 1183 DB test errors and a dead deploy.

Resolve the merge result -- base plus branch -- rather than the branch, and
assert exactly one head. Migrations are immutable and none may be deleted,
both asserted against the merge base, so the merge is the union of the two
file sets and needs no actual merge to evaluate. That also lets the check
run for any PR from any checkout, which the second half of the fix needs.

The script alone would not have caught this. GitHub re-runs a PR's checks
when the PR moves, not when its base does, and this repo does not require
branches to be up to date before merging, so the stale green was mergeable
the whole time. Every push to main now re-checks each open PR that adds a
migration and posts a commit status, so a PR that has just been made
undeployable says so on the PR.

Failures name both revisions, the file each lives in, which side each came
from, and both remedies. A branch that merely inherits a divergent main is
told it is not at fault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Peyton-Spencer Peyton-Spencer changed the title peyton/migration order merge result fix(ci): reject a PR whose merge into main would leave two Alembic heads Jul 28, 2026
@Peyton-Spencer
Peyton-Spencer marked this pull request as ready for review July 28, 2026 19:27
@Peyton-Spencer
Peyton-Spencer merged commit db7cb53 into main Jul 28, 2026
9 checks passed
@Peyton-Spencer
Peyton-Spencer deleted the peyton/migration-order-merge-result branch July 28, 2026 19:43
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant