skills/safety/ai-safety-guardrails/migration-safety/SKILL.md
CRITICAL SAFETY SKILL - Use this skill before running ANY database migration. Activate when the user wants to run migrations, rollback migrations, re-run migrations, or modify database schema. This skill prevents data loss from migrations by enforcing backup requirements, checking existing data, and requiring explicit confirmation before destructive changes.
npx skillsauth add latestaiagents/agent-skills migration-safetyInstall 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.
CRITICAL: Migrations can destroy production data. This skill prevents catastrophic data loss.
Real disaster scenarios:
migrate:fresh or migrate:reset on any database with data# Check current environment
echo $NODE_ENV
echo $RAILS_ENV
echo $APP_ENV
# Check database connection
# Laravel
php artisan db:show
# Rails
rails db:version
# Node.js (check connection string)
echo $DATABASE_URL
RED FLAGS - STOP IMMEDIATELY:
-- Before ANY migration, check what exists
SELECT table_name,
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_name = t.table_name) as columns,
(SELECT reltuples::bigint FROM pg_class
WHERE relname = t.table_name) as estimated_rows
FROM information_schema.tables t
WHERE table_schema = 'public'
ORDER BY table_name;
Present this to user:
## Current Database State
**Database:** [name]
**Environment:** [env]
**Tables:** [count]
**Total estimated rows:** [count]
| Table | Columns | Estimated Rows |
|-------|---------|----------------|
| users | 15 | 12,450 |
| orders | 23 | 89,234 |
| ... | ... | ... |
⚠️ This migration will affect a database with EXISTING DATA.
Before running, analyze each pending migration:
## Migration Analysis
### Migration: 2024_01_15_create_orders_table
**Type:** CREATE TABLE
**Risk Level:** LOW
**Existing Data Impact:** None (new table)
### Migration: 2024_01_16_drop_legacy_users
**Type:** DROP TABLE
**Risk Level:** CRITICAL ⛔
**Existing Data Impact:** Will DELETE 1,247 records permanently
**Recommendation:** BACKUP REQUIRED before proceeding
### Migration: 2024_01_17_add_email_to_users
**Type:** ADD COLUMN
**Risk Level:** LOW
**Existing Data Impact:** None (nullable column)
MANDATORY before any migration that:
# PostgreSQL
pg_dump -Fc database_name > backup_$(date +%Y%m%d_%H%M%S).dump
# MySQL
mysqldump -u user -p database_name > backup_$(date +%Y%m%d_%H%M%S).sql
# SQLite
cp database.sqlite database.sqlite.backup.$(date +%Y%m%d_%H%M%S)
# Verify backup
ls -la backup_*.{dump,sql} 2>/dev/null || ls -la *.backup.* 2>/dev/null
# Run with verbose output
php artisan migrate --pretend # Laravel: see SQL first
rails db:migrate:status # Rails: check status
npx prisma migrate deploy # Prisma: production deploy
⚠️ **DESTRUCTIVE MIGRATION WARNING**
**Migration:** 2024_01_16_drop_legacy_users
**Action:** DROP TABLE `legacy_users`
**Records that will be PERMANENTLY DELETED:** 1,247
**Backup created:** backup_20240116_143022.dump
**Backup verified:** ✓ (15.2 MB)
**Recovery command if needed:**
```bash
pg_restore -d database_name backup_20240116_143022.dump
Do you want to proceed? Type 'yes, delete 1247 records' to confirm.
### For Fresh/Reset Migrations
**NEVER suggest `migrate:fresh` or `migrate:reset` without this warning:**
```markdown
⛔ **CRITICAL WARNING: COMPLETE DATA DESTRUCTION**
You requested: `migrate:fresh` / `migrate:reset`
This will:
1. DROP ALL TABLES in the database
2. DELETE ALL DATA (estimated 156,789 records)
3. Re-run all migrations from scratch
**This is IRREVERSIBLE without a backup.**
Current database: [name]
Environment: [env]
Tables to be dropped: [count]
Records to be deleted: [count]
**Is this a development database with test data you can recreate?**
If YES and you're certain: Type 'yes, destroy all data in [database_name]'
If NO or UNSURE: Type 'cancel' and I'll help you with a safer approach.
WRONG approach:
# DO NOT just re-run!
php artisan migrate
RIGHT approach:
## Migration Failure Analysis
**Failed migration:** 2024_01_17_modify_orders
**Error:** Column 'status' cannot be dropped because of foreign key constraint
**State:** PARTIALLY APPLIED
**Current situation:**
- Migration started but did not complete
- Database may be in inconsistent state
- Re-running may cause data loss
**Safe recovery steps:**
1. **Check current state:**
```sql
SELECT * FROM migrations ORDER BY id DESC LIMIT 5;
Check what was applied:
-- Verify table structure
\d orders
Options: A) Fix the migration and run manually B) Rollback to known good state (if possible) C) Restore from backup (safest)
Recommendation: Option C - Restore from backup
Would you like me to proceed with restoring from the backup at backup_20240117_091534.dump?
### Scenario: Need to Re-run Migration
**NEVER re-run without investigation:**
```markdown
## Re-run Migration Request
You want to re-run: `2024_01_15_seed_products`
**Analysis:**
- This migration inserts seed data
- Table `products` currently has 5,234 records
- Re-running may:
- Duplicate records (if no unique constraints)
- Fail on conflicts (if unique constraints exist)
- Delete existing records (if migration truncates first)
**Migration file check:**
```php
// Found in migration:
DB::table('products')->truncate(); // ⛔ DANGEROUS - Will delete 5,234 records!
Safe alternatives:
Do NOT re-run this migration unless you want to delete 5,234 records.
## Framework-Specific Safety
### Laravel
```php
// Safe migration patterns
// GOOD: Check before truncate
public function up()
{
if (Schema::hasTable('products') && DB::table('products')->count() > 0) {
throw new Exception('Cannot run: products table has existing data');
}
// ... migration code
}
// GOOD: Soft delete instead of drop
public function up()
{
Schema::rename('legacy_users', 'legacy_users_archived_' . date('Ymd'));
}
// GOOD: Backup before destructive operation
public function up()
{
// Export existing data first
$users = DB::table('legacy_users')->get();
Storage::put('backups/legacy_users_' . date('Ymd') . '.json', $users->toJson());
Schema::dropIfExists('legacy_users');
}
# Safe migration patterns
# GOOD: Reversible migrations with safety checks
class DropLegacyUsers < ActiveRecord::Migration[7.0]
def up
count = LegacyUser.count
if count > 0
raise "Cannot drop legacy_users: #{count} records exist. Backup first!"
end
drop_table :legacy_users
end
def down
create_table :legacy_users do |t|
# ... columns
end
end
end
# GOOD: Use safety_assured for intentional destructive changes
class RemoveEmailFromUsers < ActiveRecord::Migration[7.0]
def change
safety_assured { remove_column :users, :deprecated_email }
end
end
// Safe migration patterns
// Check before migrate
async function safeMigrate() {
const count = await prisma.user.count();
if (count > 0) {
console.error(`Database has ${count} users. Create backup first!`);
process.exit(1);
}
// Run migration
execSync('npx prisma migrate deploy');
}
// Never use reset in production
if (process.env.NODE_ENV === 'production') {
console.error('prisma migrate reset is DISABLED in production');
process.exit(1);
}
# PostgreSQL
pg_restore -c -d database_name backup_file.dump
# MySQL
mysql -u user -p database_name < backup_file.sql
# SQLite
cp database.sqlite.backup database.sqlite
-- PostgreSQL with WAL archiving
SELECT pg_create_restore_point('before_migration');
-- After disaster
-- Restore to the named point
| Practice | Why |
|----------|-----|
| Always backup before migrations | Recovery from mistakes |
| Check existing data first | Awareness of impact |
| Use --pretend / dry-run | See changes before applying |
| Never use fresh/reset on data | Prevents accidental deletion |
| Verify environment | Prevents production disasters |
| Keep migrations reversible | Enables rollback |
| Test migrations on copy first | Catches issues early |
| Log migration runs | Audit trail |
Stop immediately if:
When in doubt, DON'T migrate. Ask first.
development
Test skills for correct activation, content quality, and regression — both automated checks (frontmatter validity, lint) and manual verification (query-suite activation testing). Covers CI integration and how to catch skill regressions before users do. Use this skill when adding skills to a repo, setting up CI for a skill library, or debugging "the skill exists but doesn't work". Activate when: test skills, validate skills, skill CI, skill linting, skill activation test, skill regression.
documentation
Write the YAML frontmatter for a SKILL.md file so it activates reliably — name, description, and activation keywords that the model matches against. Covers length, tone, and the most common frontmatter mistakes. Use this skill when authoring a new skill, fixing a skill that isn't auto-activating, or reviewing skills for publication. Activate when: SKILL.md frontmatter, skill description, skill activation, skill YAML, write a skill, author a skill.
development
Design skills that fire at the right moment — neither over-eager (noise) nor under-eager (silent). Covers activation specificity, trigger phrases, disambiguation between overlapping skills, and debugging activation. Use this skill when multiple skills could fire on the same query, a skill never fires, or a skill fires too often. Activate when: skill won't activate, skill over-activates, overlapping skills, skill triggers, skill selection, skill disambiguation.
development
Structure SKILL.md content so the model reads just enough — concise summary up front, progressively deeper detail, examples on demand. Covers section ordering, length budgets, when to split into multiple skills. Use this skill when writing or refactoring a skill body, one skill has grown too long, or a skill is wordy but not useful. Activate when: SKILL.md structure, skill content, skill too long, split skill, progressive disclosure, skill body.