You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
service-automation: a duration-less timer wait suspends a run forever with no job, no waitUntil and not one log line — should it warn, refuse, or stay silent? #17939
Filed by the domain:services execution seat from #17843's delivered measurement (PR #17937). ⛔ Unlabelled on purpose — domain:* and priority are triage's to set, not this seat's.
The question
Should a wait node that resolves to a timer with no duration warn, refuse, or keep suspending silently?
This is a product question with a real case on each side, ⛔ not a defect with one obvious repair — which is why it is a card and not a patch.
The measurement it rests on — observed, ⛔ not read from source
#17843 converted a source reading into a real engine.execute() run. With the whole waitEventConfig block absent — a document FlowNodeSchemaaccepts, and the state a freshly created node is in:
node: { id: 'pause', type: 'wait', label: 'Wait' }
executor return: { success: true, suspend: true, correlation: 'timer:pause', output: undefined }
engine result: { success: true, status: 'paused', runId: 'run_caa5b321-...' }
scheduled jobs: [] <- a job service WAS present and answering
stored variables: no 'pause.waitUntil' key
log lines during run: [] <- none, at ANY level
nodes that ran: [ 'before' ] <- reached the node, never got past it
⇒ eventType defaults to 'timer', the timer branch computes no deadline, no wake-up job is armed despite a job service being available, no waitUntil is persisted, and not one log line is emitted at any level. The run parks until something external calls resume(runId) — which, with no waitUntil recorded anywhere, nothing knows to do.
The control discriminates on every channel: the same node with eventType: 'timer' + timerDuration: 'PT1H' schedules a job, writes waitUntil, and carries the job's own correlation. ⭐ The one channel that does not discriminate is status: 'paused' — both halves park — so it is not a signal.
Pinned as a characterization at packages/services/service-automation/src/builtin/absent-config-node-characterization.test.ts (PR #17937). ⚠️ That file is explicitly a photograph, not an endorsement — it records the behaviour so a change to it is visible; it takes no position on this card's question.
Why it is a decision
For leaving it: wait-node.ts carries a source comment declaring the fallback deliberate — "waitEventConfig is itself optional, and a wait node without one is a VALID TIMER WAIT". Someone decided this. A wait with no duration may be a legitimate "park until externally resumed" shape, and a resume(runId) door exists.
Against: if that IS the intended shape, the silence still costs — there is no waitUntil, no job, and no log line, so an operator has no way to distinguish "deliberately parked pending an external resume" from "stuck". A run in that state is indistinguishable from a lost one.
⛔ Not objectui#9109's own question (whether those declared defaults should be deleted or backed upstream). That is needs-user-decision over there and is unaffected by the answer here.
Two adjacent observations from the same run, recorded so they are not re-derived
The wait executor's return carries output as a present key holding undefined rather than omitting it, so any JSON rendering of that value silently reads as "no output key". An observation about how the value reads, not a defect — but whatever answers this card touches that exact return shape.
A paused run emits no run-level log line at all, where a completed or failed run gets an [automation] run flow=... status=... line. Consistent with "paused is not terminal"; it is the other half of why the absent-config wait is silent end to end.
Refs
#17843 (the measurement request, answered) · PR #17937 (the characterization test) · 5651444086 (the triage seat's source reading, which the run confirmed on the wait half) · objectstack-ai/objectui#9109 (p1, the surface that leads an author here)
Filed by the
domain:servicesexecution seat from #17843's delivered measurement (PR #17937). ⛔ Unlabelled on purpose —domain:*and priority are triage's to set, not this seat's.The question
Should a
waitnode that resolves to a timer with no duration warn, refuse, or keep suspending silently?This is a product question with a real case on each side, ⛔ not a defect with one obvious repair — which is why it is a card and not a patch.
The measurement it rests on — observed, ⛔ not read from source
#17843 converted a source reading into a real
engine.execute()run. With the wholewaitEventConfigblock absent — a documentFlowNodeSchemaaccepts, and the state a freshly created node is in:⇒
eventTypedefaults to'timer', the timer branch computes no deadline, no wake-up job is armed despite a job service being available, nowaitUntilis persisted, and not one log line is emitted at any level. The run parks until something external callsresume(runId)— which, with nowaitUntilrecorded anywhere, nothing knows to do.The control discriminates on every channel: the same node with
eventType: 'timer'+timerDuration: 'PT1H'schedules a job, writeswaitUntil, and carries the job's own correlation. ⭐ The one channel that does not discriminate isstatus: 'paused'— both halves park — so it is not a signal.Pinned as a characterization at⚠️ That file is explicitly a photograph, not an endorsement — it records the behaviour so a change to it is visible; it takes no position on this card's question.
packages/services/service-automation/src/builtin/absent-config-node-characterization.test.ts(PR #17937).Why it is a decision
wait-node.tscarries a source comment declaring the fallback deliberate — "waitEventConfigis itself optional, and a wait node without one is a VALID TIMER WAIT". Someone decided this. Awaitwith no duration may be a legitimate "park until externally resumed" shape, and aresume(runId)door exists.waitUntil, no job, and no log line, so an operator has no way to distinguish "deliberately parked pending an external resume" from "stuck". A run in that state is indistinguishable from a lost one.defaultValuethe installed spec applies none of — the reconciliation ledger stops at the escalation block objectui#9109 is at p1 precisely because its inspector revealstimerDurationon a node whose config block is absent, so an author who reads it as "already a timer wait" and saves untouched ships exactly this document. It parses clean at every step and the author receives no negative signal anywhere.boundary_event. Measured separately in measurement request(flow executor): what does await/boundary_eventnode with NO config block do at run time? — the parse contract accepts it, and objectui's inspector already advertises it as a timer #17843 and it is the opposite shape — no executor is registered for the type at all, so it fails loudly withNO_EXECUTORplus a startupwarnnaming the type, and a fully populatedboundaryConfigfails identically. Loud by design; nothing to decide there.needs-user-decisionover there and is unaffected by the answer here.Two adjacent observations from the same run, recorded so they are not re-derived
waitexecutor's return carriesoutputas a present key holdingundefinedrather than omitting it, so any JSON rendering of that value silently reads as "nooutputkey". An observation about how the value reads, not a defect — but whatever answers this card touches that exact return shape.[automation] run flow=... status=...line. Consistent with "paused is not terminal"; it is the other half of why the absent-configwaitis silent end to end.Refs
#17843 (the measurement request, answered) · PR #17937 (the characterization test) ·
5651444086(the triage seat's source reading, which the run confirmed on thewaithalf) · objectstack-ai/objectui#9109 (p1, the surface that leads an author here)Generated by Claude Code