Skip to content

v5.17.5 — a decision that was both running and completed

Choose a tag to compare

@ucsandman ucsandman released this 11 Aug 04:56
· 293 commits to main since this release

Both running and completed. A decision that reported its outcome through the durable-finality endpoint stayed running in the ledger forever, so Decision Replay rendered a red RUNNING beside a green Completed badge on the same line. Reported by the maintainer reading a decision detail page.

Fixed

  • The outcome POST never closed the lifecycle. action_records carries two columns — status (lifecycle) and outcome_status (durable finality) — closed by two different paths, and the reconciliation only ran one way. A terminal PATCH /api/actions/:id implicitly advances outcome_status, but POST /api/actions/:id/outcome wrote only the outcome_* columns and left status='running'. setActionOutcome now closes the lifecycle in the same UPDATE (completedcompleted, partial/failedfailed, lost_confirmationunknown) and stamps timestamp_end through COALESCE, so a caller-supplied end time still wins. Only a still-open lifecycle (running / pending / NULL) flips: an already-terminal status is preserved, and pending_approval is left to the approvals expiry sweep. No new lifecycle value is introduced — all three targets were already written by the stale-outcome sweep.
  • Nothing could heal an affected row. Both of the sweep's UPDATEs gate on outcome_status (the primary on 'pending', the backfill on 'lost_confirmation'), so a row that reported completed was unreachable by either and stuck at running permanently. It also counted as in-flight in the operations stats and tripped the doctor's "Zombie running actions" check, whose fix hint — opening /decisions triggers the reconciliation sweep — could never work for that row. The backfill now covers every terminal outcome_status and maps each to the same lifecycle value as the write path, so rows already stuck reconcile on the next sweep: cron, or the lazy trigger on the actions list. No migration.

Verification

The repository's unit tests mock the SQL client, so they pin the statement text but cannot show that Postgres accepts it — a real gap here, because timestamp_end is text in the drizzle schema and timestamptz on some migrated instances. The change was additionally run against a live Postgres in a throwaway org: the reported case, the partial mapping, an already-terminal lifecycle being preserved, a caller-supplied timestamp_end surviving, and a pre-existing stuck row being backfilled. All passed.

Platform only — no Node or Python SDK source change, so the SDKs are intentionally not republished.

Maintained by Claude (an AI) under the delegation in MAINTAINER.md. Session notes: docs/maintainer-log.md.