Features
- Detects writes to migration, schema, and
.sql files via PostToolUse and triggers status checks automatically
- Runs
npx knex migrate:status for Node.js projects that include Knex in package.json
- Runs
python manage.py showmigrations --plan for Django projects when manage.py is present
- Flags pending Knex migrations and prompts the developer to run
npx knex migrate:latest
- Scans raw SQL files for destructive statements (DROP, DELETE, TRUNCATE) and issues warnings before execution
- Prints migration best-practice reminders (backup, test on staging, reversible migrations, use transactions)
Use Cases
- Catching unapplied Knex migrations immediately after a schema file is written during development
- Verifying Django migration state after editing a models.py or migrations file without leaving Claude Code
- Flagging accidental DROP or TRUNCATE statements in hand-written SQL before they run in a CI pipeline
- Keeping development, staging, and production schemas in sync by surfacing pending migrations early
Installation
- Create hooks directory: mkdir -p .claude/hooks
- Create hook file: touch .claude/hooks/database-migration-runner.sh
- Make executable: chmod +x .claude/hooks/database-migration-runner.sh
- Add configuration from Hook Configuration section above to .claude/settings.json or ~/.claude/settings.json
- Alternative: Use the interactive /hooks command in Claude Code
Config paths
- Local (not committed):
.claude/settings.local.json
- User settings (global):
~/.claude/settings.json
- Project-wide (committed):
.claude/settings.json
Requirements
- Claude Code CLI installed
- Project directory initialized
- Bash shell available
- jq command-line JSON processor
- Knex 3.x (Node.js) or Django 5.x (Python) installed in the project
- Node.js and npx available for Knex projects; Python and manage.py present for Django projects
Hook Configuration
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/database-migration-runner.sh",
"matchers": ["write", "edit"]
}
}
}
Hook Script
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if it's a migration-related file
if [[ "$FILE_PATH" == *migration* ]] || [[ "$FILE_PATH" == *schema* ]] || [[ "$FILE_PATH" == *.sql ]]; then
echo "🗃️ Database migration file detected: $FILE_PATH" >&2
# Check for Knex migrations
if [ -f "package.json" ] && grep -q "knex" package.json; then
echo "📦 Knex migration framework detected" >&2
# Knex migrations
if command -v npx &> /dev/null && npx knex --version &> /dev/null 2>&1; then
echo "🔧 Running Knex migration status check..." >&2
MIGRATION_STATUS=$(npx knex migrate:status 2>/dev/null || echo "No pending migrations")
echo "📊 Migration Status: $MIGRATION_STATUS" >&2
# Check for pending migrations
if echo "$MIGRATION_STATUS" | grep -q "pending"; then
echo "⚠️ Pending migrations detected. Run 'npx knex migrate:latest' to apply them" >&2
else
echo "✅ All migrations are up to date" >&2
fi
fi
# Django migrations
elif [ -f "manage.py" ]; then
echo "🐍 Django project detected" >&2
if command -v python &> /dev/null; then
echo "🔧 Checking Django migration status..." >&2
python manage.py showmigrations --plan 2>/dev/null | tail -5 | head -3 || echo "💡 Run 'python manage.py showmigrations' to check status" >&2
fi
# Raw SQL files
elif [[ "$FILE_PATH" == *.sql ]]; then
echo "📜 Raw SQL migration file detected" >&2
# Check file size and complexity
if [ -f "$FILE_PATH" ]; then
LINE_COUNT=$(wc -l < "$FILE_PATH" 2>/dev/null || echo "0")
echo "📊 SQL file contains $LINE_COUNT lines" >&2
# Check for potentially destructive operations
if grep -i "DROP\|DELETE\|TRUNCATE" "$FILE_PATH" >/dev/null 2>&1; then
echo "⚠️ WARNING: Potentially destructive SQL operations detected (DROP/DELETE/TRUNCATE)" >&2
echo "💡 Consider creating a backup before executing this migration" >&2
fi
# Check for common patterns
if grep -i "CREATE TABLE\|ALTER TABLE\|CREATE INDEX" "$FILE_PATH" >/dev/null 2>&1; then
echo "🏗️ Schema modification statements detected" >&2
fi
if grep -i "INSERT\|UPDATE" "$FILE_PATH" >/dev/null 2>&1; then
echo "📝 Data modification statements detected" >&2
fi
fi
fi
# General migration best practices reminder
echo "📋 Migration Best Practices:" >&2
echo " • Always backup database before running migrations" >&2
echo " • Test migrations on development/staging first" >&2
echo " • Ensure migrations are reversible when possible" >&2
echo " • Use transactions for atomic operations" >&2
else
echo "File $FILE_PATH is not a migration file, skipping analysis" >&2
fi
exit 0
Examples
Database Migration Runner Hook Script
Complete hook script that detects migration files and checks migration status
#!/usr/bin/env bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
if [[ "$FILE_PATH" == *migration* ]] || [[ "$FILE_PATH" == *schema* ]] || [[ "$FILE_PATH" == *.sql ]]; then
echo "Database migration file detected: $FILE_PATH" >&2
if [ -f "package.json" ] && grep -q "knex" package.json; then
if command -v npx &> /dev/null && npx knex --version &> /dev/null 2>&1; then
echo "Running Knex migration status check..." >&2
MIGRATION_STATUS=$(npx knex migrate:status 2>/dev/null || echo "No pending migrations")
echo "Migration Status: $MIGRATION_STATUS" >&2
fi
fi
fi
exit 0
Hook Configuration
Complete hook configuration for .claude/settings.json to enable database migration detection on file write/edit
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/database-migration-runner.sh",
"matchers": ["write", "edit"]
}
}
}
SQL Migration Analysis
Enhanced hook script that analyzes SQL migration files for destructive operations and schema changes
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [[ "$FILE_PATH" == *.sql ]]; then
if [ -f "$FILE_PATH" ]; then
LINE_COUNT=$(wc -l < "$FILE_PATH" 2>/dev/null || echo "0")
echo "SQL file contains $LINE_COUNT lines" >&2
if grep -i "DROP\|DELETE\|TRUNCATE" "$FILE_PATH" >/dev/null 2>&1; then
echo "WARNING: Potentially destructive SQL operations detected (DROP/DELETE/TRUNCATE)" >&2
echo "Consider creating a backup before executing this migration" >&2
fi
if grep -i "CREATE TABLE\|ALTER TABLE\|CREATE INDEX" "$FILE_PATH" >/dev/null 2>&1; then
echo "Schema modification statements detected" >&2
fi
fi
fi
exit 0
Django Migration Detection
Enhanced hook script for Django migration detection and status checking
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [[ "$FILE_PATH" == *migration* ]] && [ -f "manage.py" ]; then
echo "Django project detected" >&2
if command -v python &> /dev/null; then
echo "Checking Django migration status..." >&2
python manage.py showmigrations --plan 2>/dev/null | tail -5 | head -3 || echo "Run 'python manage.py showmigrations' to check status" >&2
fi
fi
exit 0
Troubleshooting
Hook detects migration file but Knex check fails
Verify package.json contains a knex dependency and run npm install. Check that npx knex --version succeeds. Ensure knexfile.js is present and points to the correct migrations directory.
Knex migration status shows no pending despite new files
Run npx knex migrate:list to verify migration discovery. Check that migration filenames follow the timestamp pattern Knex expects (e.g. 20240101_create_users.js). Confirm knexfile.js migrations path is correct.
PostToolUse hook triggers on non-migration SQL files
Tighten path matching by adding a directory check: [[ "$FILE_PATH" == *migrations/* ]]. Adjust or remove the *.sql pattern if raw SQL files in other directories are causing false positives.
Destructive operation warning for safe rollback scripts
The hook warns on DROP/DELETE/TRUNCATE in any SQL context. Split destructive down-migrations into separately reviewed files, or add a comment like # rollback-safe and update the grep pattern to skip flagged files.
Django migration detection fails in a virtual environment
Use the absolute Python path from which python or which python3 instead of relying on the shell PATH inside the hook. Ensure manage.py is present in the project root and the virtualenv is activated before Claude Code starts.