Skip to content

fix(ci): enforce a single Alembic migration head - #42890

Open
rusackas wants to merge 2 commits into
masterfrom
fix/enforce-single-migration-head
Open

fix(ci): enforce a single Alembic migration head#42890
rusackas wants to merge 2 commits into
masterfrom
fix/enforce-single-migration-head

Conversation

@rusackas

@rusackas rusackas commented Aug 7, 2026

Copy link
Copy Markdown
Member

SUMMARY

The existing Check DB migration conflict workflow is advisory-only: when a
migration lands on the base branch, it scans other open PRs that also
touch superset/migrations/** and posts a warning comment asking them to
rebase. It never actually computes whether the merge leaves more than one
Alembic head, and nothing about it blocks a merge.

That gap is exactly how master ended up with two divergent heads
(4f145192b583 and c4a1b8e2d739) this week — two unrelated migrations
branched off the same parent and merged independently, and nothing caught
it until flask db upgrade broke and #42878 had to land a manual merge
revision to fix it retroactively.

This adds a new workflow, enforce-single-migration-head.yml, that runs
superset db heads and fails if the count isn't exactly 1:

  • On pull_request it checks out the default merge ref (base + this PR
    combined, which is what actions/checkout gives you automatically for
    pull_request-triggered runs). flask-migrate's heads command only
    walks the migration script directory — it never touches a real database —
    so this doesn't need Postgres/Redis services, just a syntactically valid
    SQLALCHEMY_DATABASE_URI to let the app instantiate.
  • It also runs on push to master/release branches (so if a required-check
    bypass ever lets a fork through, master itself fails loudly and
    immediately instead of drifting until someone notices a broken upgrade).
  • Deliberately no paths: filter on the trigger. This is registered as
    a required status check below, and a required check that never runs for
    a given PR blocks that PR from merging forever — GitHub has no way to
    mark an "Expected" check satisfied if it never reports. So the workflow
    fires on every PR, and a first step asks the GitHub API whether this PR's
    files touch superset/migrations/ to decide whether to actually do the
    (cheap, but non-zero) work, while still always reporting a status. Matches
    the pattern dependency-review.yml already uses for the same reason.
  • Also registers enforce-single-migration-head in .asf.yaml's
    required_status_checks.contexts, so ASF Infra actually enforces it on
    master instead of it just being a check nobody's required to look at.

The existing advisory-comment job is left as-is; it's still useful context
for the "hey, someone else also touched migrations" case, just no longer
the only line of defense.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — CI workflow only.

TESTING INSTRUCTIONS

  • zizmor (GHA security audit, run via pre-commit) passes clean on both
    changed workflow files.
  • YAML validated with yaml.safe_load for the workflow and .asf.yaml.
  • Reasoned through against flask_migrate/alembic internals:
    ScriptDirectory.from_config(...).get_heads() (what the heads command
    calls) parses migration files directly and never invokes env.py, so it
    needs no DB connectivity — consistent with the existing superset db upgrade invocation pattern already used in superset-app-cli.yml, just
    without that job's Postgres/Redis services.
  • Confirmed against the repo's existing required contexts (lint-check,
    dependency-review, frontend-build) that .asf.yaml contexts match the
    job id, not a job-level name: override, so this job intentionally has
    no name: field to keep the two in sync.
  • Once merged, the easiest live test is to open two PRs with migrations
    that branch off the same parent and confirm the second one to update
    fails this check.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

🤖 Generated with Claude Code

The existing DB-migration-conflict check only posts an advisory
comment on other open PRs when a new migration lands on the base
branch; it never actually computes whether the merge leaves more
than one Alembic head. Two independent migrations that both branch
off the same parent can merge without warning and silently fork the
history until someone notices flask db upgrade is broken (see #42878,
which needed a manual merge revision after exactly this happened).

Add a job that runs `superset db heads` against the PR's merge ref
(and against master/release branches on push, as a tripwire) and
fails if the count isn't exactly 1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dosubot dosubot Bot added the github_actions Pull requests that update GitHub Actions code label Aug 7, 2026
@bito-code-review

bito-code-review Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped - Files Excluded

Bito didn't auto-review this change because all changed files are in the exclusion list for automatic reviews. No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change the excluded files settings here, or contact your Bito workspace admin at evan@preset.io.

Marking the previous job a required status check while it only ran on
`paths: superset/migrations/**` would have blocked every PR that
doesn't touch migrations forever, since a required check that never
reports can never be satisfied. Move it to its own workflow with no
path filter (matching dependency-review.yml's shape) and decide
whether migrations changed inside the job instead, via the PR's
changed-files API, so it always reports a status.

Also register it in .asf.yaml's required_status_checks so ASF Infra
actually enforces it on master.
@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 7714703
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a761cfe979a260008510d55
😎 Deploy Preview https://deploy-preview-42890--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #88c6a6

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 6d2eb1f..7714703
    • .asf.yaml
  • Files skipped - 1
    • .github/workflows/enforce-single-migration-head.yml - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

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

Labels

github_actions Pull requests that update GitHub Actions code preset-io size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants