Claude-skill-registry create-migration
Create, apply, and rollback database migrations using goose
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-migration" ~/.claude/skills/majiayu000-claude-skill-registry-create-migration && rm -rf "$T"
manifest:
skills/data/create-migration/SKILL.mdsource content
Create Migration Skill
Use this skill when making database schema changes.
Scope
- Adding new tables
- Modifying columns or constraints
- Creating or dropping indexes
- Any DDL operation
Creating a New Migration
Step 1: Generate Migration File
task migrate:create -- your_migration_name
Naming conventions:
- Use snake_case
- Be descriptive but concise
- Use prefixes:
,add_
,create_
,drop_
,modify_update_
Examples:
task migrate:create -- add_weapon_stats_table task migrate:create -- add_index_on_trader_offers task migrate:create -- modify_item_properties_column
This creates a timestamped file in
migrations/:
migrations/YYYYMMDDHHMMSS_your_migration_name.go
Step 2: Write the Migration
The generated file will have this structure:
package migrations import ( "database/sql" "github.com/pressly/goose/v3" ) func init() { goose.AddMigrationContext(upYourMigrationName, downYourMigrationName) } func upYourMigrationName(ctx context.Context, tx *sql.Tx) error { // This code is executed when the migration is applied. return nil } func downYourMigrationName(ctx context.Context, tx *sql.Tx) error { // This code is executed when the migration is rolled back. return nil }
In the
function:up
- Write SQL to apply the change
- Use
tx.ExecContext(ctx, "SQL HERE")
In the
function:down
- Write SQL to reverse the change
- Make it possible to rollback safely
Example:
func upAddWeaponStatsTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` CREATE TABLE weapon_stats ( id SERIAL PRIMARY KEY, weapon_id VARCHAR(255) NOT NULL, recoil_vertical INT NOT NULL, recoil_horizontal INT NOT NULL, ergonomics INT NOT NULL, created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_weapon_stats_weapon_id ON weapon_stats(weapon_id); `) return err } func downAddWeaponStatsTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, `DROP TABLE IF EXISTS weapon_stats;`) return err }
Step 3: Apply the Migration
# Recommended for DevContainer (assuming database is running) task migrate:up # Use if database needs to be started via Docker Compose task migrate:up:docker
What it does:
: Builds the migration binary and runs migrations against the existing database.migrate:up
: Ensures PostgreSQL is running via Docker Compose, then applies migrations.migrate:up:docker
Verify migration applied:
docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "\dt"
Step 4: Test the Migration
Test the migration works:
# Apply migration task migrate:up # Run integration tests task test:integration
Test rollback works:
# Rollback the migration task migrate:down # Verify database is in previous state docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "\dt" # Reapply task migrate:up
Migration Best Practices
DO:
- ✅ Keep migrations small and focused (one logical change per migration)
- ✅ Provide a
function that reverses the change when feasibledown - ✅ Test both
andup
migrations before mergingdown - ✅ goose wraps migrations in transactions automatically
- ✅ Add indexes for foreign keys and frequently queried columns
- ✅ Use
/IF NOT EXISTS
for safety when appropriateIF EXISTS
DON'T:
- ❌ Modify existing migration files after they're merged (create a new migration instead)
- ❌ Use application code in migrations (keep them SQL-only)
- ❌ Make data changes that can't be reversed in
down - ❌ Forget to handle the error return value
- ❌ Create huge migrations that change many things at once
Common Migration Patterns
Add a Table
func upCreateTableName(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` CREATE TABLE table_name ( id SERIAL PRIMARY KEY, field1 VARCHAR(255) NOT NULL, field2 INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW() ); `) return err } func downCreateTableName(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, `DROP TABLE IF EXISTS table_name;`) return err }
Add a Column
func upAddColumnToTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` ALTER TABLE table_name ADD COLUMN new_column VARCHAR(255); `) return err } func downAddColumnToTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` ALTER TABLE table_name DROP COLUMN IF EXISTS new_column; `) return err }
Add an Index
func upAddIndexOnTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` CREATE INDEX idx_table_column ON table_name(column_name); `) return err } func downAddIndexOnTable(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, ` DROP INDEX IF EXISTS idx_table_column; `) return err }
Troubleshooting
Migration fails with "docker": executable file not found:
- This happens if you try to run
in an environment without Docker (like a devcontainer).task migrate:up:docker - Use
instead if your database is already running.task migrate:up
Migration fails to apply:
- Check SQL syntax
- Verify table/column names exist
- Check if migration was already partially applied
- View database logs:
docker compose logs postgres
Can't rollback migration:
- Check if
function properly reverses thedown
functionup - Some operations (like dropping columns with data) might need manual intervention
- Consider if rollback is safe with existing data
Migration applied but tests fail:
- Verify the schema change matches your model expectations
- Check if indexes are created correctly
- Ensure foreign key constraints are correct
"goose: no migrations to run" but migration file exists:
- Ensure the file is in
directorymigrations/ - Check the filename format:
YYYYMMDDHHMMSS_name.go - Verify the file has
at the toppackage migrations - Rebuild:
task migrate:build
Viewing Migration Status
# See applied migrations in database docker compose exec postgres psql -U $POSTGRES_USER -d $POSTGRES_DB -c "SELECT * FROM goose_db_version;" # See migration files ls -la migrations/
CI/CD
In CI, migrations run via:
task migrate:ci
This skips the
compose:postgres:up dependency (database already running in CI).