docs/templates/skills/database-migration-guard/SKILL.md
Auto-trigger when detecting database migration files or schema changes. Validates migration safety and backwards compatibility without blocking user flow.
npx skillsauth add reefbytes-owner/manifest database-migration-guardInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill automatically activates when Claude detects changes to database schema or migration files.
Activate when files matching these patterns are modified:
Django (Python):
*/migrations/*.pymodels.py filesSQLAlchemy (Python):
*/alembic/versions/*.pyalembic/env.pyRails (Ruby):
db/migrate/*.rbdb/schema.rbNode.js (Sequelize, Knex, TypeORM):
*/migrations/*.js, */migrations/*.ts*/models/*.js, */models/*.tsGo (golang-migrate, GORM):
*/migrations/*.sql*/migrations/*.goWhen triggered, this skill performs lightweight validation:
Non-breaking (safe to deploy immediately):
Breaking (requires multi-phase deployment):
Check: Flag breaking changes and recommend multi-phase approach.
Verify that old application code can run with the new schema:
Good example (backwards compatible):
# Migration: Add new column with DEFAULT
class Migration:
def upgrade():
op.add_column('users', sa.Column('age', sa.Integer(), nullable=True, server_default='0'))
Old code doesn't know about age column → still works ✅
Bad example (breaks old code):
# Migration: Add NOT NULL column without DEFAULT
class Migration:
def upgrade():
op.add_column('users', sa.Column('age', sa.Integer(), nullable=False))
Old code tries to INSERT without age → fails ❌
Multi-phase approach for breaking changes:
# Phase 1: Add nullable column
op.add_column('users', sa.Column('age', sa.Integer(), nullable=True))
# Deploy new code that writes to both old and new columns
# Phase 2: Backfill data
op.execute('UPDATE users SET age = 0 WHERE age IS NULL')
# Deploy new code that only uses new column
# Phase 3: Make NOT NULL
op.alter_column('users', 'age', nullable=False)
# Phase 4: Remove old column (if any)
op.drop_column('users', 'old_age_field')
Check:
For migrations that transform existing data:
Required elements:
Good example:
# Migration: Normalize phone numbers
class Migration:
def upgrade():
# 1. Add new column
op.add_column('users', sa.Column('phone_normalized', sa.String(20), nullable=True))
# 2. Backfill in batches
batch_size = 1000
op.execute('''
UPDATE users
SET phone_normalized = regexp_replace(phone, '[^0-9]', '', 'g')
WHERE id IN (
SELECT id FROM users
WHERE phone_normalized IS NULL
LIMIT {batch_size}
)
'''.format(batch_size=batch_size))
# 3. Make NOT NULL after backfill
op.alter_column('users', 'phone_normalized', nullable=False)
def downgrade():
op.drop_column('users', 'phone_normalized')
Check:
For large tables (>100k rows), use concurrent index creation to avoid locking:
PostgreSQL - Good:
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
PostgreSQL - Bad (locks table during creation):
CREATE INDEX idx_users_email ON users(email);
MySQL - Good:
ALTER TABLE users ADD INDEX idx_users_email (email), ALGORITHM=INPLACE, LOCK=NONE;
Check:
Adding constraints requires care to avoid locking tables:
PostgreSQL - Good (two-phase approach):
-- Phase 1: Add constraint as NOT VALID (no table scan, fast)
ALTER TABLE users ADD CONSTRAINT users_email_check CHECK (email ~* '@') NOT VALID;
-- Phase 2: Validate in separate transaction (can be cancelled if needed)
ALTER TABLE users VALIDATE CONSTRAINT users_email_check;
PostgreSQL - Bad (locks table during full scan):
ALTER TABLE users ADD CONSTRAINT users_email_check CHECK (email ~* '@');
Check:
Every migration should have a tested rollback plan:
Required:
downgrade() or down() function implementedExample:
class Migration:
def upgrade():
op.add_column('users', sa.Column('age', sa.Integer(), nullable=True))
def downgrade():
# SAFE: Column removal doesn't break old code
# WARNING: Data in 'age' column will be lost
op.drop_column('users', 'age')
Check:
🗄️ Database Migration Guard Findings:
Migration: 20260204_add_user_age.py
✅ Backwards compatibility: PASS
- New column 'age' is nullable
- Old code can run with new schema
⚠️ Data migration: WARNING
- No backfill script for existing rows
- Recommendation: Add default value or backfill script
✅ Index strategy: PASS
- Using CREATE INDEX CONCURRENTLY
- Estimated time: 30s for 500k rows
❌ Rollback: FAIL
- No downgrade() function
- Add rollback script before deploying
Deployment recommendation: MULTI-PHASE
Phase 1: Deploy migration (add nullable column)
Phase 2: Deploy code that writes to new column
Phase 3: Backfill existing data
Phase 4: Make column NOT NULL (if needed)
# Phase 1: Add new column, dual-write
op.add_column('users', sa.Column('full_name', sa.String(), nullable=True))
# Deploy code that writes to both 'name' and 'full_name'
# Phase 2: Backfill
op.execute('UPDATE users SET full_name = name WHERE full_name IS NULL')
# Deploy code that reads from 'full_name', writes to both
# Phase 3: Remove old column
op.drop_column('users', 'name')
# Deploy code that only uses 'full_name'
# Phase 1: Stop writing to column
# Deploy code that doesn't write to 'deprecated_field'
# Phase 2: Remove column
op.drop_column('users', 'deprecated_field')
# Verify no code references the field
# Phase 1: Add new column
op.add_column('users', sa.Column('age_int', sa.Integer(), nullable=True))
# Phase 2: Dual-write
# Deploy code that writes to both 'age' (string) and 'age_int' (integer)
# Phase 3: Backfill
op.execute('UPDATE users SET age_int = CAST(age AS INTEGER) WHERE age_int IS NULL')
# Deploy code that reads from 'age_int', writes to both
# Phase 4: Remove old column
op.drop_column('users', 'age')
skills:
database-migration-guard:
enabled: true
large_table_threshold: 100000 # Rows
require_concurrent_indexes: true
require_rollback: true
templates/skills/api-security-guard - API endpoint validationtemplates/validation-overrides/database-safety.yml - Database-specific rulesdevelopment
Auto-trigger when detecting modifications to API endpoints, request handlers, authentication middleware, or input validation code. Validates API security best practices without blocking user flow.
testing
Create, edit, improve, or audit AgentSkills. Use when creating a new skill from scratch or when asked to improve, review, audit, tidy up, or clean up an existing skill or SKILL.md file. Also use when editing or restructuring a skill directory (moving files to references/ or scripts/, removing stale content, validating against the AgentSkills spec). Triggers on phrases like "create a skill", "author a skill", "tidy up a skill", "improve this skill", "review the skill", "clean up the skill", "audit the skill".
testing
Host security hardening and risk-tolerance configuration for OpenClaw deployments. Use when a user asks for security audits, firewall/SSH/update hardening, risk posture, exposure review, OpenClaw cron scheduling for periodic checks, or version status checks on a machine running OpenClaw (laptop, workstation, Pi, VPS).
testing
Create, edit, improve, or audit AgentSkills. Use when creating a new skill from scratch or when asked to improve, review, audit, tidy up, or clean up an existing skill or SKILL.md file. Also use when editing or restructuring a skill directory (moving files to references/ or scripts/, removing stale content, validating against the AgentSkills spec). Triggers on phrases like "create a skill", "author a skill", "tidy up a skill", "improve this skill", "review the skill", "clean up the skill", "audit the skill".