Apify Actor Run Failure Sentinel
Audit an Apify actor's most recent run status, alert Slack when it failed, timed out, or was aborted, and optionally launch a fresh retry run under a cost cap.
Detect MySQL XA branches stuck in the prepared state, roll back only aged orphans behind a dry-run and approval gate, then re-read XA RECOVER to prove it.
A distributed transaction that reaches the prepared state then loses its coordinator is left in limbo. It holds row and metadata locks, blocks history-list purge, stalls writers that touch the same rows. Nothing clears it on its own. XA RECOVER lists these prepared but not completed branches. This guard reads them, waits out a safety age so a branch from a live two-phase commit is never touched, rolls back only the orphaned ones behind a gate, then proves they are gone.
This blueprint was created by zkasuran.
dry_run is true and auto_remediate is false, so a plain run reports and records what it saw. It never rolls anything back.auto_remediate is true and dry_run is false. It then waits for a human to resume the Pause with approved set true. An unanswered gate reads as not approved.min_age_minutes. A real two-phase commit prepares and commits in well under a second, so a legitimate in-flight branch is never old enough to be touched.XA RECOVER shows every targeted branch gone.detect (io.kestra.plugin.jdbc.mysql.Query) runs XA RECOVER CONVERT XID. CONVERT XID returns the gtrid and bqual as a hex string so a binary xid rebuilds safely.compute (io.kestra.plugin.core.output.OutputValues) reads a KV cursor that holds the first time each branch was seen, keeps the entry for a branch still prepared, adds a timestamp for a new one, then targets only the branches first seen at least min_age_minutes ago. It rebuilds the XA ROLLBACK statement for each target from the hex gtrid, the hex bqual and the formatID.save_cursor (io.kestra.plugin.core.kv.Set) writes the pruned cursor back. It carries no ttl because an expired KV key throws on read. Pruning to the branches still prepared keeps it bounded.log_status (io.kestra.plugin.core.log.Log) records the prepared count, the targeted count and the mode.have_targets (io.kestra.plugin.core.flow.If) ends with a clean log when nothing is old enough. Otherwise alert_orphans posts to Slack in every mode.mode (io.kestra.plugin.core.flow.If) enters remediation only when auto_remediate is true and dry_run is false. Otherwise report_only logs what would run.approval_gate (io.kestra.plugin.core.flow.Pause) holds the run for a human. remediate_rollback (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) runs one XA ROLLBACK per targeted branch.reverify re-reads XA RECOVER. verify_result fails the run through io.kestra.plugin.core.execution.Fail when any targeted branch is still listed.XA RECOVER CONVERT XID output.XA RECOVER read shows every targeted branch gone.XA RECOVER plus XA ROLLBACK at 3am.MySQL has no scheduler, no age memory and no way to prove a rollback worked. Kestra adds each piece. It polls XA RECOVER on a schedule, remembers the first time each branch was seen in a KV cursor so the age gate holds across runs, gates the rollback behind dry-run and human approval, re-reads XA RECOVER to turn a hopeful rollback into a verified one. A Slack alert fires the moment an orphan appears or a step fails.
Run on Kestra 2.0.5 against a MySQL 8.0 server with real prepared XA branches forced by opening a session, running XA START, XA END, XA PREPARE, then closing the connection with no commit.
XA RECOVER showed the same three branches before and after.XA RECOVER returned empty and the seeded rows were gone, confirmed with the mysql client.min_age_minutes set to 10, a branch prepared seconds earlier was reported but not targeted, so XA RECOVER still listed it after the run.XA RECOVER read rather than reporting success.errors block.A MySQL server reachable over JDBC and a user that can run XA RECOVER and XA ROLLBACK. On MySQL 8.0 that user needs the XA_RECOVER_ADMIN privilege.
MYSQL_USERNAME: MySQL user, read when use_secret_auth is true.MYSQL_PASSWORD: password for that user.SLACK_WEBHOOK_URL: used by the alerts and the errors block.SECRET_.| Name | Type | Default | Description |
|---|---|---|---|
mysql_url |
STRING | jdbc:mysql://localhost:3306/ |
JDBC URL of the MySQL server to guard. |
use_secret_auth |
BOOL | true |
Read credentials from secrets. Set false only for inline test credentials. |
mysql_username |
STRING | root |
Username used only when use_secret_auth is false. |
mysql_password |
STRING | `` | Password used only when use_secret_auth is false. |
min_age_minutes |
INT | 15 |
Minimum time a branch must have been seen prepared before it is targeted. |
cursor_key |
STRING | xa_orphan_first_seen |
KV key for the first-seen map. Use a distinct key per guarded server. |
dry_run |
BOOL | true |
Report without rolling back. |
auto_remediate |
BOOL | false |
Roll back targeted branches behind the approval gate when dry_run is false. |
MYSQL_USERNAME, MYSQL_PASSWORD and SLACK_WEBHOOK_URL as namespace secrets.mysql_url pointed at the server. The default dry run reports prepared branches and records first-seen.schedule trigger (safe mode) for min_age_minutes so the cursor ages the branches.dry_run false and auto_remediate true, then resume the paused execution with approved true.min_age_minutes for a staging server where no real distributed transactions run. Raise it where long-lived prepared branches are expected.performance_schema.data_locks and information_schema.innodb_trx before the rollback for a richer alert.cursor_key at a per-server value and schedule one copy of the flow per MySQL instance.