Applied ML and Data Science · Mar 2026
locksmith: Dangerous Postgres Migration Checker
Ten lock rules over a real Postgres AST, blocking the migration in CI
The problem
A migration that acquires the wrong lock takes production down at deploy time.
Approach
A Go CLI that analyses migration files and flags operations acquiring dangerous locks — table rewrites, exclusive locks, blocking ALTER TABLE — before they hit production. Distributed via a Homebrew tap and designed to run in CI pipelines.
How it works
- Parses migration SQL with the actual Postgres parser via pg_query_go, turning each statement into an AST node with file and line metadata.
- A rule engine runs over those nodes, so detection is structural rather than regex matching on SQL text.
- Exits non-zero when it finds one, so a dangerous migration fails the pipeline instead of producing a report.
- Ten rules across two severities. Every finding reports the lock taken, why it blocks, and the concrete fix.
- A config file tunes rules per project, and inline ignore comments mark deliberate exceptions.
- Ships as a Homebrew tap and a GitHub Action.
Key decisions
- Fail the pipeline, do not write a report
- A warning in a log is read after the outage. A non-zero exit code stops the deploy, which is the only intervention that actually prevents the incident. Findings carry the fix, not just the rule name, so the pipeline failure is actionable on its own.
- Ignore comments over a global off switch
- Some locking migrations are intentional and scheduled. Marking them inline keeps the exception next to the reason, rather than disabling the whole rule for everyone.
What the measurements showed
- Ten rules across two severities, covering ADD COLUMN with NOT NULL and DEFAULT, ALTER COLUMN TYPE, SET NOT NULL, CREATE INDEX without CONCURRENTLY, DROP, foreign keys without NOT VALID, TRUNCATE and RENAME.
- Every finding reports the lock it takes, why it blocks, and the concrete fix, rather than just naming the rule.
- Warnings cover the quieter problems: a foreign key column with no index, or a dangerous migration with no lock_timeout set.
Running it
brew install emartai/tap/locksmithFull setup, configuration and API reference are in the repository README.
Stack
- Go
- Homebrew