The migration that takes production down is rarely the one that looks dangerous. It is an ALTER TABLE ... ALTER COLUMN ... TYPE in the middle of a file of harmless additions, or a CREATE INDEX somebody forgot to write CONCURRENTLY on, or a table used in 002 and created in 010 because the prefixes were never zero-padded. Those are all visible in the text, and a reviewer reading forty files at the end of a sprint will miss at least one.
This walkthrough uses the tool's public README and checked-in example files. Run the command from a repository checkout with Node.js 22+.
Run the checked-in example
# A careful migration set: no findings, exit 0
node bin/sql-migration-safety-checker.mjs --root examples/clean-migrations --dialect postgres
# A set that needs review: findings on stdout as JSON, summary on stderr, exit 1
node bin/sql-migration-safety-checker.mjs --root examples/risky-migrations --dialect postgres
# A dialect with no rule set: one finding, nothing parsed, exit 2
node bin/sql-migration-safety-checker.mjs --root examples/risky-migrations --dialect mysqlRead the result
stdout always carries the JSON report and nothing else, so it pipes straight into a parser. The human summary goes to stderr, and --json suppresses it. A reader that closes the pipe early — | head, or a parser that bails out — gets one line on stderr and exit 2, never a stack trace: a report that could not be delivered is an execution failure, not a success.
Scope
The size and count bounds are enforced before the work they bound, so a legal-sized input cannot exhaust memory. The time budget is checked cooperatively while walking files, during lexing, and between statements. Every bound is tested from both sides: silent at exactly the limit, and reported at one past it.