Skip to content

chore: make duplicate migration numbers impossible to ship unnoticed - #394

Merged
JasonYeYuhe merged 1 commit into
mainfrom
chore/migration-number-guard
Jul 29, 2026
Merged

chore: make duplicate migration numbers impossible to ship unnoticed#394
JasonYeYuhe merged 1 commit into
mainfrom
chore/migration-number-guard

Conversation

@JasonYeYuhe

Copy link
Copy Markdown
Collaborator

The collision

main carries migrate_v0.70_device_app_version.sqlalready applied to production. PR #393 independently added migrate_v0.70_provider_accounts.sql. Different schema, same number, and neither author was in a position to know.

Two migrations sharing a number destroys the only record of what has actually run: "did v0.70 run?" becomes "which v0.70?". Postgres applies both without complaint, so the question only gets asked months later, during an incident.

Three layers

Because the first two are advice, and advice gets missed.

  • scripts/check_migration_numbers.sh — fails on any duplicate, names both files, prints the next free number. Verified both directions: passes on main (71 migrations), and reddens with exit 1 when the real Draft: isolated macOS onboarding and multi-account QA experience #393 collision is reproduced locally.
  • A swift-ci job, next to the helper guards. The container-touch guard sat wired into nothing for months and caught three real holes the first time anyone ran it by hand — a guard nothing calls is not a guard.
  • AGENTS.md + a new CLAUDE.md. Claude Code loads CLAUDE.md automatically at session start and the repo only had AGENTS.md, so every Claude session was starting blind. CLAUDE.md points at AGENTS.md as the single source of truth rather than forking the guidance.

The rule

Never reuse a migration number — including one that is only used on a branch nobody has merged yet. Parallel work is normal here, and two sessions each taking "the next number" is precisely the failure mode.

If your branch collides with one that has landed on main, renumber yours. main wins, because its migration has usually already run against production.

Note

The guard's find/xargs pipelines use -print0/-0 — the repo path contains a space, and the first version cheerfully reported a duplicate file named cli.

🤖 Generated with Claude Code

`main` carries `migrate_v0.70_device_app_version.sql`, already applied to
production. PR #393 independently added `migrate_v0.70_provider_accounts.sql`.
Different schema, same number, neither author in a position to know.

Two migrations sharing a number destroys the only record of what has actually
run: "did v0.70 run?" becomes "which v0.70?". Postgres applies both without
complaint, so the question only gets asked months later, during an incident.

Three layers, because the first two are advice and advice gets missed:

- `scripts/check_migration_numbers.sh` — fails on any duplicate, names both
  files, and prints the next free number. Verified both ways: passes on `main`
  (71 migrations), and reddens with exit 1 when the real #393 collision is
  reproduced locally.
- Wired into swift-ci as its own job, next to the helper guards. The
  container-touch guard sat unwired for months and caught three real holes the
  first time anyone ran it — a guard nothing calls is not a guard.
- Written into AGENTS.md (Codex et al.) and a new CLAUDE.md (Claude Code loads
  it automatically at session start; the repo only had AGENTS.md, so Claude
  sessions were starting blind). CLAUDE.md points at AGENTS.md as the single
  source of truth rather than forking the guidance.

The rule as documented: never reuse a number, **including one only used on an
unmerged branch** — parallel work is normal here, and two sessions each taking
"the next number" is precisely the failure. If your branch collides with one
that has landed on `main`, renumber yours; `main`'s has usually already run
against production.

The guard's own find/xargs pipelines use `-print0`/`-0`: the repo path contains
a space, and the first version cheerfully reported a file named "cli".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@JasonYeYuhe
JasonYeYuhe merged commit 87aa7ac into main Jul 29, 2026
26 checks passed
@JasonYeYuhe
JasonYeYuhe deleted the chore/migration-number-guard branch July 29, 2026 12:22
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.

1 participant