Skip to content

runs.list cannot express a non-terminal status filter #3667

Description

@MintedKenny

Description

ListWorkflowRunsParams.status accepts a single WorkflowRunStatus, with no array and no negation:

// @workflow/world@5.0.0-beta.27 — dist/runs.d.ts:154
export interface ListWorkflowRunsParams {
    workflowName?: string;
    status?: WorkflowRunStatus;
    pagination?: PaginationOptions;
    resolveData?: ResolveData;
}

So there is no supported way to ask a World for the runs that are not terminal. A caller must restate the non-terminal set itself and issue one paginated list() per status.

reenqueueActiveRuns already does exactly that:

// dist/recovery.js:15
for (const status of ['pending', 'running']) {
    ...
    const page = await runs.list({ status, resolveData: 'none', ... });

The vocabulary to express this cleanly already exists in the same module:

// dist/runs.d.ts:12-19
export declare const TerminalWorkflowRunStatusSchema: z.ZodEnum<{ completed; failed; cancelled }>;
export declare const TERMINAL_WORKFLOW_RUN_STATUSES: ("completed" | "failed" | "cancelled")[];
export declare function isTerminalWorkflowRunStatus(status: string): status is TerminalWorkflowRunStatus;

and WorkflowRunSchema's discriminated union already names the complement as its non-final branch (status: z.enum(['pending', 'running']), dist/runs.js:120).

Impact

Any consumer that reconciles run state at startup or on a schedule must hardcode the runtime's status set, and that coupling is invisible to schema-level drift checks. workflow_runs.status is a Postgres enum, so changing its labels alters neither information_schema.columns nor any column type:

  • 0004_remove_run_pause_status.sql removed the paused label and renamed the replacement type back to status.
  • 0015_move_enums_to_workflow_schema.sql moves status, step_status, and wait_status from public into the workflow schema.

Both pass a column-level fingerprint untouched, so a consumer holding a hardcoded non-terminal list would silently select the wrong set afterward rather than failing loudly.

This is the same runs.list({ status, ... }) surface that #2978 identifies as unscoped in startup recovery.

Proposed direction

Let the caller express the set rather than restate it — for example status?: WorkflowRunStatus | WorkflowRunStatus[], or a terminal?: boolean discriminator implemented over the existing TERMINAL_WORKFLOW_RUN_STATUSES. Either keeps ownership of the status vocabulary inside the package, and would let reenqueueActiveRuns drop its per-status loop.

Happy to open a PR if you have a preferred shape.

Checked against @workflow/world@5.0.0-beta.27 and @workflow/world-postgres@5.0.0-beta.34.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions