Documentation Index
Fetch the complete documentation index at: https://mintlify.com/JorgeMedinaArauna/OpenClaw-Mission_control/llms.txt
Use this file to discover all available pages before exploring further.
OpenClaw Mission Control uses Alembic for database schema migrations. This guide covers migration workflows, commands, and best practices.
Overview
Migrations are stored in backend/migrations/versions/ and manage schema evolution over time. The migration system:
- Tracks applied migrations in the
alembic_version table
- Supports forward migrations (
upgrade) and rollbacks (downgrade)
- Auto-generates migration scripts from SQLModel schema changes
- Validates migration graph consistency in CI
Migration Files
Directory Structure
backend/
├── alembic.ini # Alembic configuration
├── migrations/
│ ├── env.py # Alembic environment setup
│ ├── versions/ # Migration scripts
│ │ ├── 658dca8f4a11_init.py
│ │ ├── 99cd6df95f85_add_indexes_for_board_memory_task_.py
│ │ ├── a1e6b0d62f0c_drop_org_name_unique.py
│ │ ├── b4338be78eec_add_composite_indexes_for_task_listing.py
│ │ └── ...
alembic.ini
Configuration file at backend/alembic.ini:
[alembic]
script_location = migrations
prepend_sys_path = .
sqlalchemy.url = driver://user:pass@localhost/dbname # Overridden by env.py
The sqlalchemy.url is dynamically set by migrations/env.py using the DATABASE_URL environment variable.
migrations/env.py
Runtime configuration that:
- Loads
app.models to discover SQLModel schemas
- Reads
DATABASE_URL from app.core.config.settings
- Normalizes database URL (
postgresql:// → postgresql+psycopg://)
- Configures online/offline migration modes
Key function:
def get_url() -> str:
"""Return the normalized SQLAlchemy database URL for Alembic."""
return _normalize_database_url(settings.database_url)
Running Migrations
Apply All Pending Migrations
Upgrade to the latest schema:
cd backend
.venv/bin/alembic upgrade head
Docker Compose:
docker compose exec backend alembic upgrade head
Makefile shortcut:
Apply Specific Migration
Upgrade to a specific revision:
cd backend
.venv/bin/alembic upgrade b4338be78eec # Revision ID
Rollback Migrations
Downgrade to a previous revision:
cd backend
.venv/bin/alembic downgrade -1 # Go back one revision
Downgrade to a specific revision:
cd backend
.venv/bin/alembic downgrade a1e6b0d62f0c
Rollback all migrations (dangerous!):
cd backend
.venv/bin/alembic downgrade base
Rolling back to base will drop all tables. Only use this in development or when explicitly recreating the database.
Check Current Schema Version
cd backend
.venv/bin/alembic current
Output:
View Migration History
cd backend
.venv/bin/alembic history --verbose
Automatic Migrations on Startup
The DB_AUTO_MIGRATE environment variable controls whether migrations run automatically when the backend starts:
# Enable auto-migration (useful in dev)
DB_AUTO_MIGRATE=true
# Disable auto-migration (recommended in production)
DB_AUTO_MIGRATE=false
Default behavior:
ENVIRONMENT=dev: DB_AUTO_MIGRATE defaults to true
- All other environments:
DB_AUTO_MIGRATE defaults to false
Production recommendation: Set DB_AUTO_MIGRATE=false and run migrations manually before deploying new code.
Creating New Migrations
Auto-Generate from Schema Changes
When you modify SQLModel models in backend/app/models/, generate a migration:
cd backend
.venv/bin/alembic revision --autogenerate -m "add user email field"
Alembic will:
- Compare current database schema with SQLModel definitions
- Generate a migration script in
migrations/versions/
- Detect added/removed tables, columns, indexes, and constraints
Example output:
INFO [alembic.autogenerate.compare] Detected added column 'users.email'
Generating /path/to/backend/migrations/versions/abc123def456_add_user_email_field.py ... done
Manual Migration
Create an empty migration template:
cd backend
.venv/bin/alembic revision -m "custom data migration"
Edit the generated file in migrations/versions/ to add custom logic:
def upgrade() -> None:
# Custom upgrade logic
op.execute("""
UPDATE tasks SET status = 'pending' WHERE status IS NULL
""")
def downgrade() -> None:
# Custom downgrade logic
pass
Review Generated Migrations
Always review auto-generated migrations before applying:
def upgrade() -> None:
# ### commands auto generated by Alembic ###
op.add_column('users', sa.Column('email', sa.String(), nullable=True))
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic ###
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_column('users', 'email')
# ### end Alembic commands ###
Check for:
- Unintended schema changes
- Missing
NOT NULL constraints
- Reversibility of
downgrade() function
- Data migrations that need custom logic
Migration Validation (CI)
The Makefile includes a comprehensive migration validation target:
make backend-migration-check
This target:
- Validates migration graph: Checks for cycles and orphaned revisions using
scripts/check_migration_graph.py
- Tests upgrade path: Spins up a temporary PostgreSQL container and applies all migrations
- Tests downgrade path: Rolls back all migrations to
base
- Tests re-upgrade: Applies all migrations again to verify idempotency
Example CI workflow (from .github/workflows/ci.yml):
- name: Validate database migrations
run: make backend-migration-check
Local Migration Testing
Test migrations against a clean database:
# Start a temporary Postgres container
docker run -d --rm --name mc-test-db \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=migration_test \
-p 55432:5432 \
postgres:16
# Wait for database to be ready
sleep 5
# Run migrations
cd backend
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:55432/migration_test \
.venv/bin/alembic upgrade head
# Test downgrade
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:55432/migration_test \
.venv/bin/alembic downgrade base
# Clean up
docker rm -f mc-test-db
Common Migration Operations
Add a Column
def upgrade() -> None:
op.add_column('tasks', sa.Column('priority', sa.Integer(), nullable=True))
def downgrade() -> None:
op.drop_column('tasks', 'priority')
Add an Index
def upgrade() -> None:
op.create_index('ix_tasks_status', 'tasks', ['status'])
def downgrade() -> None:
op.drop_index('ix_tasks_status', table_name='tasks')
Add a Composite Index
def upgrade() -> None:
op.create_index(
'ix_tasks_board_status',
'tasks',
['board_id', 'status'],
unique=False
)
def downgrade() -> None:
op.drop_index('ix_tasks_board_status', table_name='tasks')
Rename a Column
def upgrade() -> None:
op.alter_column('tasks', 'old_name', new_column_name='new_name')
def downgrade() -> None:
op.alter_column('tasks', 'new_name', new_column_name='old_name')
Data Migration
from alembic import op
import sqlalchemy as sa
def upgrade() -> None:
# Add new column
op.add_column('tasks', sa.Column('assignee_id', sa.String(), nullable=True))
# Migrate data
connection = op.get_bind()
connection.execute(
sa.text("""
UPDATE tasks
SET assignee_id = (SELECT user_id FROM assignments WHERE assignments.task_id = tasks.id LIMIT 1)
""")
)
def downgrade() -> None:
op.drop_column('tasks', 'assignee_id')
Drop a Table (with Safety Check)
def upgrade() -> None:
# Check if table is empty before dropping
connection = op.get_bind()
result = connection.execute(sa.text("SELECT COUNT(*) FROM deprecated_table"))
if result.scalar() > 0:
raise Exception("Cannot drop deprecated_table: contains data")
op.drop_table('deprecated_table')
def downgrade() -> None:
# Recreate table structure
op.create_table(
'deprecated_table',
sa.Column('id', sa.Integer(), primary_key=True),
sa.Column('name', sa.String(), nullable=False)
)
Production Deployment Workflow
Pre-Deployment Migration
-
Review pending migrations:
cd backend
.venv/bin/alembic history
-
Backup database:
pg_dump -U postgres mission_control > backup_$(date +%Y%m%d_%H%M%S).sql
-
Apply migrations:
.venv/bin/alembic upgrade head
-
Verify migration:
.venv/bin/alembic current
psql -U postgres mission_control -c "\dt" # List tables
-
Deploy application code:
systemctl --user restart mission-control-backend
Zero-Downtime Migrations
For large tables or production systems:
-
Make schema changes backward-compatible:
- Add nullable columns first, populate them, then add
NOT NULL constraint
- Create new tables/indexes before dropping old ones
-
Use separate migration steps:
# Step 1: Add new column (nullable)
alembic revision --autogenerate -m "add email column nullable"
alembic upgrade head
# Step 2: Backfill data
alembic revision -m "backfill email data"
# Edit migration to populate email field
alembic upgrade head
# Step 3: Add NOT NULL constraint
alembic revision --autogenerate -m "make email not null"
alembic upgrade head
-
Test in staging before applying to production
Troubleshooting
Migration Failed Mid-Execution
Alembic may leave the database in an inconsistent state. Check the alembic_version table:
SELECT * FROM alembic_version;
If the version is incorrect:
# Manually set version (use with caution)
cd backend
.venv/bin/alembic stamp <correct_revision_id>
Migration Conflicts After Merge
If two branches created migrations in parallel, you may have multiple heads:
cd backend
.venv/bin/alembic heads
Merge the heads:
.venv/bin/alembic merge <revision1> <revision2> -m "merge migration branches"
“Can’t locate revision identified by ‘xxxxx’”
The migration file is missing from migrations/versions/. Either:
- The migration was deleted (restore from git)
- You’re on the wrong branch (switch to correct branch)
- Your working directory is wrong (use
cd backend)
Database URL Not Found
Ensure DATABASE_URL is set in backend/.env:
grep DATABASE_URL backend/.env
Or set it temporarily:
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/mission_control \
.venv/bin/alembic upgrade head
Rollback Data Loss
Downgrade operations may lose data. Always:
- Backup before downgrading
- Review
downgrade() logic in migration files
- Test rollback in staging before production
Best Practices
- Always review auto-generated migrations before committing
- Test migrations locally before pushing to production
- Backup production database before applying migrations
- Keep migrations reversible when possible
- Use descriptive migration messages:
"add user email field" not "update schema"
- Avoid editing applied migrations: Create a new migration instead
- Run
make backend-migration-check in CI to catch migration issues early
- Coordinate with team when creating migrations to avoid conflicts
Reference
Next Steps