Found while browser-verifying #1078 (PR #1404) on a fresh pnpm dev boot. Out of that card's scope, filed rather than fixed.
What happened
One scheduled run of case_sla_monitor failed terminally on the first boot of a fresh database, before anyone touched the app:
ERROR Trigger-fired run of flow 'case_sla_monitor' failed (trigger 'schedule')
— no caller holds this result and nothing retries the run; the terminal
failure is recorded in the flow's run history …
Node 'notify_team' failed: notify: at least one recipient is required,
but every recipient template resolved to nothing: {currentCase.owner_id}.
src/flows/case-sla-monitor.flow.ts, the notify_team node inside the loop_cases body, addresses exactly one recipient:
recipients: ['{currentCase.owner_id}'],
The engine's own advice in that message ("address the id directly") is already what the flow does — the template is correct. What is missing is the case where the id is not there: crm_case.owner_id is nullable, an unowned case is an ordinary state (this repo ships scripts/backfill-owner-id.ts and a pnpm backfill:owner script precisely because ownerless rows happen), and a breached case with no owner leaves the node with an empty recipient slate, which the notify node treats as a hard failure.
Why it is worth a card rather than a shrug
The failure is not scoped to the one case. It is the run that fails, and the run is the scheduled sweep that flags every breached case — so the SLA monitor stops doing its job for the rest of that tick, and nothing retries it. There is no in-product signal either: the terminal failure lands in the flow's run history and the boot diagnostics, not anywhere a service manager looks.
The specific boot I saw it on had the seeder still running in the background ("Inline seed exceeded 8000ms budget for app.objectstack.hotcrm; continuing in background"), so the case the sweep picked up was very likely mid-seed with owner_id not yet stamped — by the time I queried, all 38 seeded cases had owners and none was ownerless. That makes the observed trigger a boot race, but it is not what makes it a defect: any ownerless breached case reaches the same node the same way, and that state is reachable through ordinary REST creation and import.
Not verified, for triage
- Whether the loop aborts on the first failing iteration or continues and reports at the end — I saw one run fail, not which of the loop's cases were skipped. That decides how bad this is.
- Whether
case_escalation / case_escalation_on_create have the same hole: they use {caseRecord.owner_id} in the same single-recipient shape, so they look exposed to the same empty slate, but I did not observe them fail.
Shape of a fix, for whoever picks it up
Two candidates, and they are not equivalent:
- Guard the branch — extend the condition that already gates
flag_breach / notify_team so the notify only runs when the case has an owner. Cheap, and it silently drops the alert for exactly the cases most likely to be neglected.
- Give the node a fallback recipient — a role or position (service manager) so an unowned breached case escalates to someone rather than nowhere. That is the behaviour a service org would expect, and it is a product decision, not a mechanical fix.
Recommendation: 2, with 1 as the safety net, but it wants a maintainer's call on who an ownerless breach should reach.
Measured on @objectstack/runtime 17.1.0, origin/main @ 4c6add4.
Found while browser-verifying #1078 (PR #1404) on a fresh
pnpm devboot. Out of that card's scope, filed rather than fixed.What happened
One scheduled run of
case_sla_monitorfailed terminally on the first boot of a fresh database, before anyone touched the app:src/flows/case-sla-monitor.flow.ts, thenotify_teamnode inside theloop_casesbody, addresses exactly one recipient:The engine's own advice in that message ("address the id directly") is already what the flow does — the template is correct. What is missing is the case where the id is not there:
crm_case.owner_idis nullable, an unowned case is an ordinary state (this repo shipsscripts/backfill-owner-id.tsand apnpm backfill:ownerscript precisely because ownerless rows happen), and a breached case with no owner leaves the node with an empty recipient slate, which the notify node treats as a hard failure.Why it is worth a card rather than a shrug
The failure is not scoped to the one case. It is the run that fails, and the run is the scheduled sweep that flags every breached case — so the SLA monitor stops doing its job for the rest of that tick, and nothing retries it. There is no in-product signal either: the terminal failure lands in the flow's run history and the boot diagnostics, not anywhere a service manager looks.
The specific boot I saw it on had the seeder still running in the background ("Inline seed exceeded 8000ms budget for app.objectstack.hotcrm; continuing in background"), so the case the sweep picked up was very likely mid-seed with
owner_idnot yet stamped — by the time I queried, all 38 seeded cases had owners and none was ownerless. That makes the observed trigger a boot race, but it is not what makes it a defect: any ownerless breached case reaches the same node the same way, and that state is reachable through ordinary REST creation and import.Not verified, for triage
case_escalation/case_escalation_on_createhave the same hole: they use{caseRecord.owner_id}in the same single-recipient shape, so they look exposed to the same empty slate, but I did not observe them fail.Shape of a fix, for whoever picks it up
Two candidates, and they are not equivalent:
flag_breach/notify_teamso the notify only runs when the case has an owner. Cheap, and it silently drops the alert for exactly the cases most likely to be neglected.Recommendation: 2, with 1 as the safety net, but it wants a maintainer's call on who an ownerless breach should reach.
Measured on
@objectstack/runtime17.1.0,origin/main@4c6add4.