backend/scripts/data_migrations/. They handle one-off corrections, backfills, or clean-ups that cannot be expressed as a zero-downtime Alembic migration.
This page covers how to run the scripts. The rationale for each individual migration — the problem it fixes and what it changes, plus any relevant details such as conflict handling or idempotency — lives in the module docstring at the top of the corresponding script in
backend/scripts/data_migrations/. Read that before running one.How they run
Recent migrations are wired into the container startup script (backend/scripts/start/app.sh) and run automatically on every deploy, after Alembic migrations are applied. They are idempotent (no-op once the data is corrected) and non-fatal - a failure logs a warning and retries on the next startup.
This automatic execution is temporary. Each entry in app.sh is gated by a TODO: Remove this after ~<date> comment and is deleted once existing deployments have had time to upgrade. After that point the script is no longer invoked on startup.
The practical consequence: if you upgrade Open Wearables long after a migration shipped, the corresponding script may already have been removed from app.sh and will not run automatically. In that case, run it manually (see below). Scripts that were never added to app.sh must always be run manually.
Run-once scripts
Some scripts are expensive to run even when there is nothing left to fix, because checking means scanning a large table. These run at most once per instance: a clean run writes a row with the script’s name to thedata_migration table, and from then on the script prints already completed, skipping and exits, both on startup and when run manually. A run that fails or is interrupted writes nothing, so it is retried on the next startup. When several replicas start at once, only one runs the script; the others skip it.
To run such a script again, delete its row first:
--dry-run never writes the row.
Prerequisites
The scripts import from theapp package and connect to the database via the environment variables in backend/config/.env. You need:
- Docker Compose services running (
docker compose up -d) - The
appcontainer healthy (check withdocker compose ps) - A database with the latest Alembic migrations applied (
docker compose exec app uv run alembic upgrade head)
Running a script
All scripts are run inside theapp container using uv run:
Dry run first
Every script supports a--dry-run flag. Always run it first — it prints the rows that would be affected without making any changes:

