Skip to content

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

Description

@claude

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 FlowNodeSchema accepts, 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.
  • The blast radius is real, not hypothetical: finding(app-shell): four flow-node fields declare a defaultValue the installed spec applies none of — the reconciliation ledger stops at the escalation block objectui#9109 is at p1 precisely because its inspector reveals timerDuration on 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.

⚠️ What this card is NOT

Two adjacent observations from the same run, recorded so they are not re-derived

  1. 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.
  2. 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)


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions