Found while implementing #9378 (trigger-route status unification, PR #9413). Filed rather
than fixed there: different defect class (a missing field, not a status classification),
and it changes what a successful run returns, which #9378's ruling does not cover.
Measured
packages/services/service-automation/src/engine.ts, origin/main @ bc6434b.
AutomationResult declares the pair as a general terminal-result feature
(packages/spec/src/contracts/automation-service.ts):
Friendly terminal messages copied from the flow definition
(`flow.successMessage` / `flow.errorMessage`) so a screen-flow runner can
show a meaningful toast instead of a generic "Done" / the raw error.
`successMessage` is set on terminal success, `errorMessage` on failure.
Only ONE producer honours it. Grepping the engine for both names:
successMessage: flow.successMessage — one site, inside resumeInternal's terminal
success return.errorMessage: flow.errorMessage — one site, inside resumeInternal's terminal
failure return.
execute() sets neither, on either exit — not on its success return, not on its failure
return, and not on executeWithoutRetry / retryExecution. So the author's own text
reaches a caller only when the run happened to pause and be resumed. A flow dispatched
straight through POST /api/v1/automation/:name/trigger (or the legacy
POST /automation/trigger/:name, i.e. client.automation.trigger()) never carries it,
even though the flow declares it and the contract says it is set.
Why it matters now
PR #9413 maps a failed trigger run to 400 FLOW_FAILED and carries errorMessage into
error.details.errorMessage — the one documented location the console reads (objectui
flowResponse.ts, PR #4899). That plumbing is correct and inherited verbatim from #8684,
but on the trigger path the field is always absent at the source, so the console falls
back to the raw node error for every non-screen flow. The same holds for successMessage
on the 200 path.
Not decided here
Whether execute() should set both (symmetry with resumeInternal, which is the obvious
reading of the contract text) or whether the contract text should narrow to say these are
screen-flow/resume-only. One of the two is true; the current state — declared generally,
produced on one path — is the shape ADR-0049's enforce-or-remove exists to stop.
Refs
Generated by Claude Code
Found while implementing #9378 (trigger-route status unification, PR #9413). Filed rather
than fixed there: different defect class (a missing field, not a status classification),
and it changes what a successful run returns, which #9378's ruling does not cover.
Measured
packages/services/service-automation/src/engine.ts,origin/main@bc6434b.AutomationResultdeclares the pair as a general terminal-result feature(
packages/spec/src/contracts/automation-service.ts):Only ONE producer honours it. Grepping the engine for both names:
successMessage: flow.successMessage— one site, insideresumeInternal's terminalsuccess return.
errorMessage: flow.errorMessage— one site, insideresumeInternal's terminalfailure return.
execute()sets neither, on either exit — not on its success return, not on its failurereturn, and not on
executeWithoutRetry/retryExecution. So the author's own textreaches a caller only when the run happened to pause and be resumed. A flow dispatched
straight through
POST /api/v1/automation/:name/trigger(or the legacyPOST /automation/trigger/:name, i.e.client.automation.trigger()) never carries it,even though the flow declares it and the contract says it is set.
Why it matters now
PR #9413 maps a failed trigger run to
400 FLOW_FAILEDand carrieserrorMessageintoerror.details.errorMessage— the one documented location the console reads (objectuiflowResponse.ts, PR #4899). That plumbing is correct and inherited verbatim from #8684,but on the trigger path the field is always absent at the source, so the console falls
back to the raw node error for every non-screen flow. The same holds for
successMessageon the 200 path.
Not decided here
Whether
execute()should set both (symmetry withresumeInternal, which is the obviousreading of the contract text) or whether the contract text should narrow to say these are
screen-flow/resume-only. One of the two is true; the current state — declared generally,
produced on one path — is the shape ADR-0049's enforce-or-remove exists to stop.
Refs
triggerroutes still answer HTTP 200 wrapping an inner {success:false} — the same #3962 residue #8684 closed on resume #9378 / PR fix(automation): answer real HTTP status codes on both trigger routes #9413 — the trigger-route status unification that surfaced itGenerated by Claude Code