Skip to content

listWorkflowSteps returns started/completedAtEpochMs as a string (BIGINT uncast), unlike getWorkflowStatus #1284

Description

@vikyw89

Summary

listWorkflowSteps (and the underlying getAllOperationResults) returns startedAtEpochMs / completedAtEpochMs as strings rather than numbers, because the operation_outputs.started_at_epoch_ms / completed_at_epoch_ms columns are BIGINT and node-postgres returns int8 (OID 20) as a string by default — and the step mapper passes the raw row value through without a Number() cast.

This contradicts the declared type (WorkflowStepStatus.startedAtEpochMs?: number) and is inconsistent with the workflow-status path, which does cast.

Version

@dbos-inc/dbos-sdk@4.19.8

The inconsistency

Workflow-status mapper casts (src/system_database.js ~line 199):

startedAtEpochMs: row.started_at_epoch_ms ? Number(row.started_at_epoch_ms) : undefined,

Step mapper does not (src/workflow_management.js ~line 35-36, in listWorkflowSteps):

startedAtEpochMs: step.started_at_epoch_ms,      // raw BIGINT-as-string from pg
completedAtEpochMs: step.completed_at_epoch_ms,

getAllOperationResults runs SELECT * FROM operation_outputs ... and returns rows straight from the pool, so the BIGINT columns arrive as strings.

Impact / repro

Any consumer that treats the field as the documented number breaks. Concretely:

const steps = await DBOS.listWorkflowSteps(id);
new Date(steps[0].startedAtEpochMs).toISOString();
// startedAtEpochMs is "1751015751123" (string)
// new Date("1751015751123") parses it as a date STRING, not epoch ms
//   => Invalid Date  => RangeError: Invalid Date  on .toISOString()

Steps with a NULL epoch (the common case) slip through; steps that actually have a recorded start/complete time (queued/recovered steps) trigger it.

Suggested fix

Cast in the step mapper to match getWorkflowStatus:

startedAtEpochMs: step.started_at_epoch_ms != null ? Number(step.started_at_epoch_ms) : undefined,
completedAtEpochMs: step.completed_at_epoch_ms != null ? Number(step.completed_at_epoch_ms) : undefined,

(Epoch-ms fits in a double well past year 275760, so Number() is lossless here.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions