Fix Alembic multiple heads — migration branch conflict #337

Closed
opened 2026-04-04 22:43:50 +00:00 by forgejo_admin · 3 comments

Type

Bug

User Story

story:WS-S1 As superadmin, I want to deploy platform changes via IaC so that infrastructure is reproducible and auditable.

What Broke

basketball-api pod is in CrashLoopBackOff. On startup, Alembic runs upgrade head and fails because multiple head revisions exist. The numbered migration scheme (023-030) has a branch conflict with a hash-named migration (e09c9e678004_add_division_column_to_players.py).

ERROR [alembic.util.messaging] Multiple head revisions are present for given argument 'head'
FAILED: Multiple head revisions are present for given argument 'head'

Repro Steps

  1. Pod restarts (any reason — deploy, OOM, node drain)
  2. Entrypoint runs alembic upgrade head
  3. Alembic finds 2 head revisions and aborts
  4. Pod crashes, kubelet retries, CrashLoopBackOff

Expected Behavior

alembic upgrade head succeeds with exactly 1 head. Pod starts normally.

Environment

  • basketball-api namespace, k3s cluster
  • Alembic migrations in alembic/versions/
  • Numbered scheme 023-030 + hash-named e09c9e678004

Lineage

  • Story: story:WS-S1
  • Arch: arch:basketball-api
  • Blocked by: Nothing
  • Blocks: All basketball-api functionality

Repo

forgejo_admin/basketball-api

Context

The crash pre-existed but was masked by the previous pod staying alive. PR #334 merge triggered a new deployment which restarted the pod and exposed it.

File Targets

  • alembic/versions/ — identify the two head revisions, run alembic merge heads
  • alembic/versions/e09c9e678004_add_division_column_to_players.py — likely branch point

Acceptance Criteria

  • alembic heads returns exactly 1 head
  • alembic upgrade head succeeds
  • Pod starts without CrashLoopBackOff

Test Expectations

  • alembic check passes
  • Existing tests pass
  • Pod healthcheck passes

Constraints

  • No data loss, merge migration must be additive only

Checklist

  • Identify both head revisions
  • Run alembic merge heads
  • Verify locally
  • Push and verify pod starts
  • basketball-api#334 (triggered pod restart that exposed this)
### Type Bug ### User Story `story:WS-S1` As superadmin, I want to deploy platform changes via IaC so that infrastructure is reproducible and auditable. ### What Broke basketball-api pod is in CrashLoopBackOff. On startup, Alembic runs `upgrade head` and fails because multiple head revisions exist. The numbered migration scheme (023-030) has a branch conflict with a hash-named migration (`e09c9e678004_add_division_column_to_players.py`). ``` ERROR [alembic.util.messaging] Multiple head revisions are present for given argument 'head' FAILED: Multiple head revisions are present for given argument 'head' ``` ### Repro Steps 1. Pod restarts (any reason — deploy, OOM, node drain) 2. Entrypoint runs `alembic upgrade head` 3. Alembic finds 2 head revisions and aborts 4. Pod crashes, kubelet retries, CrashLoopBackOff ### Expected Behavior `alembic upgrade head` succeeds with exactly 1 head. Pod starts normally. ### Environment - basketball-api namespace, k3s cluster - Alembic migrations in `alembic/versions/` - Numbered scheme 023-030 + hash-named `e09c9e678004` ### Lineage - **Story:** `story:WS-S1` - **Arch:** `arch:basketball-api` - **Blocked by:** Nothing - **Blocks:** All basketball-api functionality ### Repo `forgejo_admin/basketball-api` ### Context The crash pre-existed but was masked by the previous pod staying alive. PR #334 merge triggered a new deployment which restarted the pod and exposed it. ### File Targets - `alembic/versions/` — identify the two head revisions, run `alembic merge heads` - `alembic/versions/e09c9e678004_add_division_column_to_players.py` — likely branch point ### Acceptance Criteria - `alembic heads` returns exactly 1 head - `alembic upgrade head` succeeds - Pod starts without CrashLoopBackOff ### Test Expectations - `alembic check` passes - Existing tests pass - Pod healthcheck passes ### Constraints - No data loss, merge migration must be additive only ### Checklist - [ ] Identify both head revisions - [ ] Run `alembic merge heads` - [ ] Verify locally - [ ] Push and verify pod starts ### Related - basketball-api#334 (triggered pod restart that exposed this)
Author
Owner

Scope Review: READY

Review note: review-819-2026-04-04
Ticket is fully scoped with verified file targets, complete traceability, and concrete acceptance criteria. Root cause confirmed: migration e09c9e678004 forks from revision 005, creating two heads (030 and e09c9e678004). Single-repo fix, estimated 2-3 min agent time. Ready for dispatch.

## Scope Review: READY Review note: `review-819-2026-04-04` Ticket is fully scoped with verified file targets, complete traceability, and concrete acceptance criteria. Root cause confirmed: migration `e09c9e678004` forks from revision `005`, creating two heads (`030` and `e09c9e678004`). Single-repo fix, estimated 2-3 min agent time. Ready for dispatch.
Author
Owner

Resolution: Already fixed by PR #338

Investigation confirms this issue was already resolved by PR #338 (fix: resolve alembic migration chain conflict (031 → 031/032/033)), which was merged as commit 9e81e6d.

Root cause (corrected)

The scope review identified e09c9e678004 forking from revision 005 as the root cause, but that was incorrect. Migration 007 has always had down_revision = "e09c9e678004" since its creation (commit 8ed1380), making the chain 005 → e09c9e678004 → 007 linear with no fork.

The actual root cause was two separate migrations both claiming revision ID 031 with down_revision = "030":

  • 031_add_contract_overrides_to_players.py
  • 031_add_contract_email_type_enum_values.py

These were introduced by PRs #335 and #336 merging independently, each branching from 030.

Verification

Current origin/main (commit 8d0eac3):

  • alembic heads returns exactly 1 head: 035
  • Full chain is linear: 001 → ... → 005 → e09c9e678004 → 007 → ... → 030 → 031 → 032 → 033 → 034 → 035
  • Single base: 001

Timeline

  1. PR #334 merged → pod restarted → exposed the bug (but #334 itself had no migrations)
  2. PRs #335 and #336 merged nearly simultaneously → duplicate 031 revisions → multiple heads
  3. PR #338 fixed it by renumbering to 032 and 033

No further code changes needed. Closing as resolved.

## Resolution: Already fixed by PR #338 Investigation confirms this issue was already resolved by PR #338 (`fix: resolve alembic migration chain conflict (031 → 031/032/033)`), which was merged as commit `9e81e6d`. ### Root cause (corrected) The scope review identified `e09c9e678004` forking from revision `005` as the root cause, but that was incorrect. Migration `007` has always had `down_revision = "e09c9e678004"` since its creation (commit `8ed1380`), making the chain `005 → e09c9e678004 → 007` linear with no fork. The actual root cause was **two separate migrations both claiming revision ID `031` with `down_revision = "030"`**: - `031_add_contract_overrides_to_players.py` - `031_add_contract_email_type_enum_values.py` These were introduced by PRs #335 and #336 merging independently, each branching from `030`. ### Verification Current `origin/main` (commit `8d0eac3`): - `alembic heads` returns exactly **1 head**: `035` - Full chain is linear: `001 → ... → 005 → e09c9e678004 → 007 → ... → 030 → 031 → 032 → 033 → 034 → 035` - Single base: `001` ### Timeline 1. PR #334 merged → pod restarted → exposed the bug (but #334 itself had no migrations) 2. PRs #335 and #336 merged nearly simultaneously → duplicate `031` revisions → multiple heads 3. PR #338 fixed it by renumbering to `032` and `033` No further code changes needed. Closing as resolved.
Author
Owner

Validation: PASS

Tiers executed: Tier 1 (local), Tier 3 (prod)
Validation note: validation-337-2026-04-05b

5 checks: 5 PASS, 0 FAIL

# Criterion Result
1 alembic heads returns exactly 1 head PASS — single head at 035, chain linearized
2 alembic upgrade head succeeds PASS — pod startup logs clean, no errors
3 Pod starts without CrashLoopBackOff PASS — Running 1/1, 0 restarts
4 Woodpecker pipeline green PASS — pipeline #344 success
5 Health endpoint responds 200 PASS — /healthz returns {"status":"ok"}

Regression check: /docs, /public/schedule, /metrics all 200. Keycloak JWKS fetch OK. No error logs.

## Validation: PASS Tiers executed: Tier 1 (local), Tier 3 (prod) Validation note: `validation-337-2026-04-05b` **5 checks: 5 PASS, 0 FAIL** | # | Criterion | Result | |---|-----------|--------| | 1 | `alembic heads` returns exactly 1 head | PASS — single head at `035`, chain linearized | | 2 | `alembic upgrade head` succeeds | PASS — pod startup logs clean, no errors | | 3 | Pod starts without CrashLoopBackOff | PASS — Running 1/1, 0 restarts | | 4 | Woodpecker pipeline green | PASS — pipeline #344 success | | 5 | Health endpoint responds 200 | PASS — `/healthz` returns `{"status":"ok"}` | Regression check: `/docs`, `/public/schedule`, `/metrics` all 200. Keycloak JWKS fetch OK. No error logs.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/basketball-api#337
No description provided.