skilly. Buy ad slot
All skills
Uncategorized / AGENT SKILL

alembic-migrations

stritti/nak-district-planner
0 installs 0 GitHub stars
0

Use when creating, reviewing, or fixing Alembic database migrations. Covers revision chain management, multi-head avoidance, merge migrations, PostgreSQL identifier length limits,…
Use when creating, reviewing, or fixing Alembic database migrations. Covers revision chain management, multi-head avoidance, merge migrations, PostgreSQL identifier length limits, and CI checks. Also use when debugging "Multiple head revisions" errors or FK name length failures.

Low community score

alembic-migrations: This skill has a community score below −5, meaning it has received more downvotes than upvotes. Review its source and assessment before installing. Community votes are not a security review.

BEFORE YOU INSTALL

Understand the trade-offs.

SECURITY REVIEW

Not yet assessed

Review the original instructions and requested permissions before installing.

No security review is available for this catalog entry yet.

SKILL QUALITY

Not yet assessed

How clearly the skill guides your agent, how complete its workflow is, and how you can check the outcome.

No quality assessment is available for this catalog entry yet.

The full skill.

Original instructions from the publisher’s SKILL.md

# Alembic Migration Conventions

This project uses Alembic with async SQLAlchemy 2.0 + PostgreSQL 15+.

## Revision Chain Rules

### Heads: Exactly One

The migration chain MUST have exactly one head at all times. Multiple heads
break `alembic upgrade head` and block CI/CD.

```bash
# Check locally
alembic heads
# Expected output: <revision> (head)  — exactly one line
```

### When Branching Is Necessary

If you must create a migration from a non-tip revision (e.g., backporting a
hotfix to an older state), you MUST also create a **merge migration**
immediately afterward.

**Merge migration template:**

```python
"""merge <description>

Revision ID: ae6c055cc0XX
Revises: <rev1>, <rev2>
Create Date: 2026-06-29 12:00:00.000000
"""
from __future__ import annotations
from collections.abc import Sequence

revision: str = "ae6c055cc0XX"
down_revision: str | None = ("<rev1>", "<rev2>")
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None

def upgrade() -> None:
    pass

def downgrade() -> None:
    pass
```

**Existing merge examples in the project:**
- `ae6c055cc050_merge_heads.py` — merges `0010_leader_registrations` and `4c9a2d7e8f10`
- `ed967c737376_merge_planning_model_with_main_chain.py` — merges `0011_planning_model_foundation` and `e4b2a9d7f110`
- `ae6c055cc051_merge_0125_with_0012.py` — 3-way merge of `0012`, `0125_remove_events_add_sync_fields`, and `e5a2b3c4d5f0`

### Dependency Ordering: Respect Table Creation Order

`down_revision` determines when a migration runs in the topological order.
If your migration references a table, the `down_revision` chain MUST include
the migration that **creates** that table as an ancestor.

**Bad — references table that doesn't exist yet:**
```python
# 0123_matrix_migration_add_planning_slot_fields.py
down_revision = "faecec299731"   # ← branches BEFORE planning_slots was created

def upgrade():
    op.add_column("planning_slots", ...)   # ❌ relation "planning_slots" does not exist
```

**Good — chain includes the table-creating migration:**
```python
# 0123_matrix_migration_add_planning_slot_fields.py (fixed)
down_revision = "ed967c737376"   # ← merge that brought planning_slots into main chain

def upgrade():
    op.add_column("planning_slots", ...)   # ✅ table exists
```

**Rule of thumb:** If you reference a table or column that was added in a
migration from a different branch, anchor your migration at or after the
**merge point** that brought that feature in, not at a revision before it.

To find where a table was created:
```bash
grep -l "create_table('your_table'" alembic/versions/*.py
```

### Naming Convention

Migration filenames: `<revision>_<short_description>.py`

- Use sequential 4-digit prefixes for main-chain migrations (`0001_`, `0002_`, ..., `0012_`)
- Use 4-hex-digit or random-looking IDs for branch or ad-hoc migrations (`ae6c055cc050`, `e5a2b3c4d5f0`)
- Merge migrations: `<revision>_merge_<description>.py`

## Revision ID Length Limit

Alembic stores the current revision in a `VARCHAR(32)` column. Revision IDs
must be **32 characters or fewer** — otherwise `alembic upgrade head` fails
with `value too long for type character varying(32)`.

**Bad — 46 characters:**
```python
revision = "0123_matrix_migration_add_planning_slot_fields"  # ❌
```

**Good — 4 digits:**
```python
revision = "0123"  # ✅
```

This applies to the `revision` field in the migration file AND to its
references in `down_revision` of dependent migrations and merge migrations.

## PostgreSQL FK Name Limit

PostgreSQL limits identifiers (including constraint names) to **63 characters**.

An FK name following the convention
`fk_<source_table>_<local_column>_<target_table>` can easily exceed this.

**Bad — 64 characters:**
```python
op.create_foreign_key(
    "fk_planning_slots_invitation_source_congregation_id_congregations",
    ...
)
```

**Good — 55 characters:**
```python
op.create_foreign_key(
    "fk_planning_slots_inv_src_congregation_id_congregations",
    ...
)
```

**Check locally:**
```bash
# Find all FK names in migration files with length >= 60 (safety buffer)
python3 -c "
import re, sys
for f in sys.argv[1:]:
    with open(f) as fh:
        for i, line in enumerate(fh, 1):
            m = re.search(r'\"(fk_\w{50,})\"', line)
            if m:
                name = m.group(1)
                if len(name) > 55:
                    print(f'{f}:{i}: {name} ({len(name)} chars)')
" alembic/versions/*.py
```

**Always count before writing FK names:**
```bash
echo -n "fk_your_table_your_column_referenced_table" | wc -c
```

## ENUM Type Handling in Async Context

**Problem:** A migration that calls `sa.Enum(...).create(op.get_bind())`
followed by a column definition with `create_type=False` on the same type
can fail with `type "xxx" already exists` in async/asyncpg Alembic context.
The standalone `.create()` and column `create_type=False` do not coordinate
correctly, causing the column to attempt re-creating the type.

**Good — Let the column definition create the enum automatically:**
```python
op.create_table(
    "audit_logs",
    sa.Column(
        "action",
        sa.Enum(
            "CREATE", "UPDATE", "DELETE",
            name="auditaction",          # ← type created here
        ),                                # ← no create_type=False
        nullable=False,
    ),
)
```

**Bad — Separated create + create_type=False:**
```python
# ❌ Standalone create ...
sa.Enum("CREATE", "UPDATE", name="auditaction").create(op.get_bind())

op.create_table(
    "audit_logs",
    sa.Column(
        "action",
        sa.Enum(
            "CREATE", "UPDATE",
            name="auditaction",
            create_type=False,  # ❌ ... and then column tries to re-create it anyway
        ),
    ),
)
```

**Rule:** If a migration creates an ENUM type, let the column definition
create it via `sa.Enum(...)` inside the column. Do NOT add a separate
`sa.Enum(...).create(op.get_bind())` call AND do NOT add `create_type=False`
to the column definition. SQLAlchemy's column ENUM creation is idempotent
within a single `op.create_table()` call.

## Verification Checklist

Before committing migration changes:

- [ ] `alembic heads` shows exactly one `(head)` line
- [ ] `alembic upgrade head --sql` exits with code 0
- [ ] No FK constraint name exceeds 63 characters (grep for `fk_` names)
- [ ] `downgrade()` function is implemented (mirrors upgrade)
- [ ] Migration applies cleanly on a fresh database (`alembic upgrade head`)
- [ ] No `sa.Enum(...).create(op.get_bind())` + `create_type=False` anti-pattern

## CI

A GitHub Actions workflow (`.github/workflows/alembic-check.yml`) runs
automatically on every PR to verify head count, FK name lengths, and offline
SQL generation. It also runs `alembic upgrade head` on a fresh PostgreSQL
service container to ensure production-like DB apply passes.

Skill folder

Files included alongside SKILL.md in the publisher’s repository.