Neon migrations run against the wrong branch in CI
The point of a branch per PR is to run migrations against an isolated copy. If the migration step reads the production DATABASE_URL instead of the branch URL, it either mutates main or fails against schema it should not touch.
What this error means
Migrations succeed but the changes land on the main branch, or a migration fails with unexpected existing objects, because the step used the wrong connection string.
Applying migration 0007_add_orders...
ERROR: relation "orders" already exists
(migration ran against main, not the fresh pr-123 branch)Common causes
The migration step used the default DATABASE_URL
A repository secret DATABASE_URL points at main. The migration step never overrode it with the per-PR branch string, so it targeted production.
The branch URL was set after the migration step
Ordering put the connection-string derivation after the migrate step, so migrate saw the old value.
How to fix it
Export the branch URL before migrating
- Create the branch, then derive its connection string.
- Export that value as DATABASE_URL for the migrate step.
- Run migrations, then tests, all against the branch.
URL=$(neonctl connection-string "pr-${{ github.event.number }}" \
--project-id "$NEON_PROJECT_ID" --pooled)
echo "DATABASE_URL=$URL" >> "$GITHUB_ENV"
npm run migrateAssert the branch before applying
Fail fast if the connection string still points at main so a misconfiguration cannot mutate production.
neonctl branches get "pr-${{ github.event.number }}" --project-id "$NEON_PROJECT_ID" >/dev/nullHow to prevent it
- Derive and export the branch connection string before the migrate step.
- Never point CI migrations at the shared main DATABASE_URL.
- Verify the branch exists before running migrations against it.