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
fix(v2): derive the log and run status enums from the persisted status list (#6612)
* fix(v2): derive the log and run status enums from the persisted status list
`GET /api/v2/logs` and `GET /api/v2/logs/{runId}` parse the raw
`workflow_execution_logs.status` column against a six-value enum that omits
`paused`, so a run holding that value returns 500. The list response is
validated whole-page, so one such row 500s every page it lands on, and the
row is durable until the run is resumed, cancelled, or failed.
`paused` is not written by an ordinary human-in-the-loop pause — that path
persists `pending` (logging-session.ts:1180). It is written by
`PauseResumeManager.markResumeAttemptFailed`, which fires on any
`ResumeAdmissionError`: a workspace over its usage limit, an archived or
undeployed workflow, or a concurrent resume losing the claim race. That is a
routine business path.
The enum was supposed to be protected by an `AssertNever` exhaustiveness gate,
but the gate was vacuous: it compared against `PersistedWorkflowExecutionStatus`,
a hand-written union that was itself missing `paused`, because the write goes
through a raw `sql` CASE fragment Drizzle cannot type-check. Adding `paused` to
both lists would leave the same vacuous gate in place for the next status.
Instead, `PERSISTED_WORKFLOW_EXECUTION_STATUSES` becomes the single runtime
source of truth, `PersistedWorkflowExecutionStatus` is derived from it, and both
v2 contracts derive their enums from the const rather than re-declaring them.
Both surfaces pass the column through verbatim, so their reported set is the
persisted set by definition — there is no editorial choice for a gate to force,
only the question of whether a newly persisted status should be public, which
the option-list tests now pin. The `[...V2_PERSISTED_RUN_STATUSES, 'paused']`
append on the runs contract is deleted rather than adjusted; it would otherwise
be a duplicate.
Alternatives rejected:
- A `.catch()` or `safeParse` in the presenters is dead code:
`v2-json-route.ts:271` re-parses the whole body with the same schema.
- Normalizing `markResumeAttemptFailed` to write `pending` would remove the
distinction the resume claim query at human-in-the-loop-manager.ts:973 relies
on, and leaves the contract wrong for any other future status.
- Typing the Drizzle column does not help: the offending write is a raw `sql`
fragment, and `packages/db` cannot import the app's status list.
The v2 workflows spec changes are reordering and description only — the value
set there already contained `paused`. The v2 logs spec gains `paused`, which is
additive and safe while the whole `/api/v2` surface is behind the off-by-default
`v2-api` flag; it must land before v2 GA, after which it would be breaking.
* fix(v2): document both provenances of a reported paused run status
* fix(v2): stop promising a paused discriminator the response cannot always provide
* fix(v2): describe the paused discriminator as the code actually records it
"description": "Current execution status. `redacting` is transient while run output is scrubbed."
643
+
"enum": [
644
+
"pending",
645
+
"running",
646
+
"paused",
647
+
"redacting",
648
+
"completed",
649
+
"failed",
650
+
"cancelled"
651
+
],
652
+
"description": "Current execution status. `redacting` is transient while run output is scrubbed. `paused` is reported when a resume attempt did not run to completion and the run is waiting to be resumed again."
"description": "Current execution status. `redacting` is transient while run output is scrubbed."
1039
+
"enum": [
1040
+
"pending",
1041
+
"running",
1042
+
"paused",
1043
+
"redacting",
1044
+
"completed",
1045
+
"failed",
1046
+
"cancelled"
1047
+
],
1048
+
"description": "Current execution status. `redacting` is transient while run output is scrubbed. `paused` is reported when a resume attempt did not run to completion and the run is waiting to be resumed again."
Copy file name to clipboardExpand all lines: apps/docs/openapi-v2-workflows.json
+5-5Lines changed: 5 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -3923,13 +3923,13 @@
3923
3923
"enum": [
3924
3924
"pending",
3925
3925
"running",
3926
+
"paused",
3926
3927
"redacting",
3927
3928
"completed",
3928
3929
"failed",
3929
-
"cancelled",
3930
-
"paused"
3930
+
"cancelled"
3931
3931
],
3932
-
"description": "Current or terminal run status. `redacting` is transient, reported while the output of a finished run is being scrubbed."
3932
+
"description": "Current or terminal run status. `redacting` is transient, reported while the output of a finished run is being scrubbed. `paused` means the run is not executing and is waiting to be resumed: either held at a human-in-the-loop pause point, or left paused because a resume attempt did not run to completion. The status alone does not say which. On the single-run response `paused.automaticResumeWaitingReason` distinguishes them: it is recorded whenever a resume attempt fails and cleared once a resume succeeds, so a null value means the run is waiting on human input. When the failure is not retryable or the automatic retries are exhausted, the reason is prefixed `Automatic resume requires manual intervention: `. Run-list items carry no `paused` object, so the two cases are indistinguishable there."
3933
3933
},
3934
3934
"trigger": {
3935
3935
"type": "string",
@@ -4063,14 +4063,14 @@
4063
4063
"enum": [
4064
4064
"pending",
4065
4065
"running",
4066
+
"paused",
4066
4067
"redacting",
4067
4068
"completed",
4068
4069
"failed",
4069
4070
"cancelled",
4070
-
"paused",
4071
4071
"queued"
4072
4072
],
4073
-
"description": "Current or terminal run status. `redacting` is transient, reported while the output of a finished run is being scrubbed."
4073
+
"description": "Current or terminal run status. `redacting` is transient, reported while the output of a finished run is being scrubbed. `paused` means the run is not executing and is waiting to be resumed: either held at a human-in-the-loop pause point, or left paused because a resume attempt did not run to completion. The status alone does not say which. On the single-run response `paused.automaticResumeWaitingReason` distinguishes them: it is recorded whenever a resume attempt fails and cleared once a resume succeeds, so a null value means the run is waiting on human input. When the failure is not retryable or the automatic retries are exhausted, the reason is prefixed `Automatic resume requires manual intervention: `. Run-list items carry no `paused` object, so the two cases are indistinguishable there."
.describe('Current execution status. `redacting` is transient while run output is scrubbed.')
32
+
.enum(PERSISTED_WORKFLOW_EXECUTION_STATUSES)
33
+
.describe(
34
+
'Current execution status. `redacting` is transient while run output is scrubbed. `paused` is reported when a resume attempt did not run to completion and the run is waiting to be resumed again.'
35
+
)
49
36
50
37
/** Execution `files` is a per-run jsonb array of attachment metadata. */
'Current or terminal run status. `redacting` is transient, reported while the output of a finished run is being scrubbed. `paused` means the run is not executing and is waiting to be resumed: either held at a human-in-the-loop pause point, or left paused because a resume attempt did not run to completion. The status alone does not say which. On the single-run response `paused.automaticResumeWaitingReason` distinguishes them: it is recorded whenever a resume attempt fails and cleared once a resume succeeds, so a null value means the run is waiting on human input. When the failure is not retryable or the automatic retries are exhausted, the reason is prefixed `Automatic resume requires manual intervention: `. Run-list items carry no `paused` object, so the two cases are indistinguishable there.'
1006
987
1007
988
/**
1008
-
* The list projection overlays `paused` onto the persisted status whenever the run has a
1009
-
* `paused` or `partially_resumed` row in `paused_executions`. It cannot report `queued`:
1010
-
* a run that is still only in the job queue has no log row to list.
989
+
* The list projection passes `workflow_execution_logs.status` through except where it
990
+
* overlays `paused` for a run holding a `paused` or `partially_resumed` row in
991
+
* `paused_executions` — so a reported `paused` is either that overlay or the persisted
992
+
* value a failed resume attempt left behind. Both branches land in the persisted set, so the reported enum is
993
+
* derived from it — a value missing here fails the response parse, and because list
994
+
* validation is whole-page one such row turns an entire page into a 500. `queued` is not
995
+
* reportable: a run still only in the job queue has no log row to list.
0 commit comments