Symptom
After a process restart, an in-flight paused automation run cannot be enumerated or read through the automation API, even though the run survived, is listed correctly over the data API, and resumes perfectly.
Measured on one paused run (showcase_budget_approval, parked at its approval_revise node):
| read | result |
|---|
GET /api/v1/data/sys_automation_run/run_1a7f3a5b… | 200, status:'paused', node_id:'wait_revision', node_type:'approval_revise' |
GET /api/v1/automation/showcase_budget_approval/runs | 200 with zero rows |
GET /api/v1/automation/showcase_budget_approval/runs?status=paused | 200 with zero rows |
GET /api/v1/automation/showcase_budget_approval/runs/run_1a7f3a5b… | 404 RESOURCE_NOT_FOUND "Execution not found" |
Reproduced 2×, across 3 durable paused rows on 2 flows (showcase_reassign_wizard showed durable = 2, listedPaused = 0).
Reproduction
- Boot the showcase on a file DB:
os dev -a dist/objectstack.json --seed-admin -d file:/tmp/x/data.db -p <port> - Park a run — e.g.
PATCH a showcase_project's budget above the approval threshold, then POST /api/v1/approvals/requests/:id/revise, which parks showcase_budget_approval at its approval_revise node. - Kill the process and cold-boot a second server over the same DB file.
- Read the row over the data API and over the automation API.
Expected both surfaces show the paused run.
Actual the data API shows it; the automation API lists zero and 404s the detail.
Root cause (suspected, located)
AutomationEngine.listRuns merges the in-memory ring buffer with the durable history rows (the run_run_<id>-prefixed terminal rows written by recordLog) — but not the durable paused rows (the run_<id> rows written by the suspended-run store, packages/services/service-automation/src/suspended-run-store.tsserialize()).
Before a restart, paused runs appear only because they are still in the ring. After a restart the ring is empty and the paused rows have no reader.
Scope — deliberately narrow
Durability itself is intact, and this is not data loss. A post-restart paused screen run still served GET /runs/:runId/screen (200, full contract) and POST /runs/:runId/resume (200 → run completed, acted:1, paused row consumed, history row written). Eight paused rows across five suspending node types round-tripped a cold restart byte-identical.
This is an observability defect: after any process cycle an operator cannot enumerate in-flight runs through the automation API. It also silently defeats a filter that just landed — #7359's newly-enforced ?status=paused can never return a row after a restart, which is the one query an operator would reach for here.
Suggested shape (not prescriptive)
Have listRuns (and the by-id read) also consult the suspended-run store's durable rows, deduping against the ring by run id. The gate: a test that parks a run, cold-restarts against the same file DB, and asserts the run appears in ?status=paused and answers on run-detail — the existing durability tests assert resumability, which is exactly the half that already works.
Source
Found during the platform checklist retest of automation.durable-suspend-restart (framework 279ee48a). Did not hold any clause of that item — the item's own steps read the paused rows over the data API, which works.
Symptom
After a process restart, an in-flight paused automation run cannot be enumerated or read through the automation API, even though the run survived, is listed correctly over the data API, and resumes perfectly.
Measured on one paused run (
showcase_budget_approval, parked at itsapproval_revisenode):GET /api/v1/data/sys_automation_run/run_1a7f3a5b…status:'paused',node_id:'wait_revision',node_type:'approval_revise'GET /api/v1/automation/showcase_budget_approval/runsGET /api/v1/automation/showcase_budget_approval/runs?status=pausedGET /api/v1/automation/showcase_budget_approval/runs/run_1a7f3a5b…Reproduced 2×, across 3 durable paused rows on 2 flows (
showcase_reassign_wizardshowed durable = 2, listedPaused = 0).Reproduction
os dev -a dist/objectstack.json --seed-admin -d file:/tmp/x/data.db -p <port>PATCHashowcase_project's budget above the approval threshold, thenPOST /api/v1/approvals/requests/:id/revise, which parksshowcase_budget_approvalat itsapproval_revisenode.Expected both surfaces show the paused run.
Actual the data API shows it; the automation API lists zero and 404s the detail.
Root cause (suspected, located)
AutomationEngine.listRunsmerges the in-memory ring buffer with the durable history rows (therun_run_<id>-prefixed terminal rows written byrecordLog) — but not the durable paused rows (therun_<id>rows written by the suspended-run store,packages/services/service-automation/src/suspended-run-store.tsserialize()).Before a restart, paused runs appear only because they are still in the ring. After a restart the ring is empty and the paused rows have no reader.
Scope — deliberately narrow
Durability itself is intact, and this is not data loss. A post-restart paused screen run still served
GET /runs/:runId/screen(200, full contract) andPOST /runs/:runId/resume(200 → run completed,acted:1, paused row consumed, history row written). Eight paused rows across five suspending node types round-tripped a cold restart byte-identical.This is an observability defect: after any process cycle an operator cannot enumerate in-flight runs through the automation API. It also silently defeats a filter that just landed — #7359's newly-enforced
?status=pausedcan never return a row after a restart, which is the one query an operator would reach for here.Suggested shape (not prescriptive)
Have
listRuns(and the by-id read) also consult the suspended-run store's durable rows, deduping against the ring by run id. The gate: a test that parks a run, cold-restarts against the same file DB, and asserts the run appears in?status=pausedand answers on run-detail — the existing durability tests assert resumability, which is exactly the half that already works.Source
Found during the platform checklist retest of
automation.durable-suspend-restart(framework279ee48a). Did not hold any clause of that item — the item's own steps read the paused rows over the data API, which works.