New to Kestra?
Use blueprints to kickstart your first workflows.
Safely release an orphaned Liquibase changelog lock after approval and verify the database can plan pending SQL.
A lock left by a killed migration blocks every later deployment. Releasing a lock while its migration is still running can corrupt DATABASECHANGELOG, so this flow only proposes a lock older than the configured grace period and pauses before releaseLocks.
| Guard | What it stops |
|---|---|
stale_after_seconds |
A live migration whose lock is still within its normal runtime |
Snapshot comparison after Pause |
Acting on a holder, timestamp or pending set that changed while approval was waiting |
auto_remediate plus dry_run |
Writes from the shipped defaults and disabled schedule |
Empty-lock and updateSQL verification |
Calling a successful CLI exit a recovery when the lock remains or SQL cannot be planned |
inspect_before (io.kestra.plugin.liquibase.CLI) runs listLocks and status --verbose against the target changelog. It records the holder, granted timestamp, age, pending count, exit codes and a snapshot hash.findings (io.kestra.plugin.core.flow.If) logs an empty table, a legitimate young lock or a stale candidate. A healthy database and a migration still inside the grace period are spared.remediation_mode requires auto_remediate: true and dry_run: false. It posts the stale age and pending count to Slack then waits at approval_gate (io.kestra.plugin.core.flow.Pause).recheck_before_write repeats both CLI reads after approval. stale_plan_guard compares its snapshot with the original and fails before any write when the target changed.release_lock (io.kestra.plugin.liquibase.CLI) runs Liquibase releaseLocks for the same changelog. It is the only mutating task.verify_release reads listLocks and status again then runs updateSQL. verify_result fails unless the lock table is empty and all reads plus SQL planning succeed.every_15_minutes schedule forces the safe inputs. The errors block sends a Slack alert for any failed detection, safety gate, write or verification.DATABASECHANGELOGLOCK by hand.A shell alias can run releaseLocks, but it cannot preserve the pre-approval snapshot, refuse a changed target, record who approved the write or fail when the database still cannot plan SQL. Kestra keeps the CLI reads, approval, guarded write and independent verification in one execution history.
Tested against Kestra 2.0.5, Liquibase 4.29.2 and PostgreSQL 16 in a Docker test fixture. The fixture seeds two changesets, forces DATABASECHANGELOGLOCK with a one-hour-old holder and leaves one changeset pending. The remediation path measures one held lock before, zero locks after, one pending changeset before and a successful updateSQL plan after. Healthy, dry-run, declined, ineffective, repeated and approval-race cases are recorded in the evidence manifest for this blueprint.
| Case | Execution | Result |
|---|---|---|
| Healthy | 7JEW2E3aa3P61CX54UBzCL |
SUCCESS. No lock held, pending changeset read, nothing written |
| Dry run | 79XuWzgJSIhfxR2bjXyWl3 |
SUCCESS. One-hour-old lock found, lock count still 1 |
| Declined approval | 494kvhHyzeGcfuzu19S272 |
SUCCESS. Decline logged, lock count still 1 |
| Remediation | APusFXL4laAM0qRGk31YA |
SUCCESS. Lock count 1 to 0, updateSQL plans the pending changeset, applied changesets kept |
| Repeat | 5BPm8aKrISTmoc2CcoJg6G |
SUCCESS. Lock table already empty, no write |
| Release refused | 3wVzdDW30A698E9fKG421Z |
FAILED as intended. Verification gate stopped the run, lock count still 1 |
| Holder changed during the Pause | 5bCbriKDgneQcxwAVpWUEL |
FAILED as intended. New holder detected before releaseLocks, lock count still 1 |
DATABASECHANGELOGLOCK row is managed by Liquibase. Replace the example changelog_yaml variable with the changelog used by the deployment.releaseLocks.LIQUIBASE_JDBC_URL: JDBC URL for the target database.LIQUIBASE_USERNAME: Liquibase database user.LIQUIBASE_PASSWORD: Liquibase database password.SLACK_WEBHOOK_URL: Slack incoming webhook for candidate and failure alerts.| Input | Default | Purpose |
|---|---|---|
stale_after_seconds |
900 |
Minimum lock age before a candidate is proposed |
auto_remediate |
false |
Enables the approval and write branch |
dry_run |
true |
Keeps the run read-only when true |
docker_network |
bridge |
Docker network used by the worker task runner |
changelog_yaml with the production changelog.inspect_before.auto_remediate: true and dry_run: false, inspect the Slack alert, then resume the Pause with approved: true.updateSQL output needed for the next investigation.every_15_minutes only after confirming its forced safe-mode inputs are appropriate for your namespace.stale_after_seconds from the longest observed migration plus a review margin.Limits: lock age is parsed from Liquibase's human-readable listLocks output, so a locale or CLI format change that cannot be parsed fails closed and does not release anything. The flow does not prove that a discarded migration left no partial external side effects; inspect the failed migration before approving.
This blueprint was created by zkasuran.
