mastra-ai/mastra · warning · HTTPException

runId required to stream workflow

Error message

runId required to stream workflow

What it means

A 400 request-validation error thrown by the workflow stream handler when the `runId` parameter is missing. After workflowId is validated, the handler rejects streaming requests without a runId because the stream is attached to an existing run created via createRun (or starts one only when a runId can be resolved).

Solutions

  1. Always call createRun() first and pass its runId to the stream call.
  2. Await createRun before opening the stream to avoid the undefined-runId race.
  3. Check your @mastra/client-js version's stream() signature — pass runId via the expected parameter (body field or path).
  4. Add a falsy guard on runId before dispatching the stream request.

Example fix

// before
const runPromise = wf.createRun({});
const stream = await wf.stream({ runId: runPromise.runId }); // not awaited -> undefined
// after
const { runId } = await wf.createRun({});
const stream = await wf.stream({ runId });
Defensive patterns

Strategy: validation

Validate before calling

export async function streamWithRun(client: MastraClient, workflowId: string, runId?: string) {
  const id = runId ?? (await client.getWorkflow(workflowId).createRun({})).runId;
  if (!id) throw new Error('runId required to stream workflow');
  return client.getWorkflow(workflowId).stream({ runId: id });
}

Type guard

function hasRunHandle(r: { runId?: string } | undefined): r is { runId: string } {
  return typeof r?.runId === 'string' && r.runId.length > 0;
}

Try / catch

try {
  const stream = await wf.stream({ runId });
} catch (e) {
  if (e instanceof MastraClientError && e.status === 400 && /runid/i.test(e.message)) {
    const { runId: fresh } = await wf.createRun({});
    return wf.stream({ runId: fresh });
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/workflows/{workflowId}/stream without runId in path/body, calling `workflow.stream()` without first obtaining a runId, or a race where the stream call fires before createRun resolves.

Common situations: UI starts streaming before createRun's promise resolves (runId still null); custom transport drops the runId body field; SDK version mismatch where stream() no longer auto-creates runs.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/2f89903d416140b3. Report an issue: GitHub.

Appendix: source

Thrown at packages/server/src/server/handlers/workflows.ts:572

  responseType: 'stream',
  pathParamSchema: workflowIdPathParams,
  queryParamSchema: runIdSchema,
  bodySchema: streamWorkflowBodySchema,
  summary: 'Stream workflow execution',
  description: 'Executes a workflow and streams the results in real-time',
  tags: ['Workflows'],
  requiresAuth: true,
  handler: async ({ mastra, workflowId, runId, resourceId, requestContext, ...params }) => {
    try {
      // Use effective resourceId (context key takes precedence over client-provided value)
      const effectiveResourceId = getEffectiveResourceId(requestContext, resourceId);

      if (!workflowId) {
        throw new HTTPException(400, { message: 'Workflow ID is required' });
      }

      if (!runId) {
        throw new HTTPException(400, { message: 'runId required to stream workflow' });
      }

      const { workflow } = await listWorkflowsFromSystem({ mastra, workflowId });

      if (!workflow) {
        throw new HTTPException(404, { message: 'Workflow not found' });
      }

      const existingRun = await workflow.getWorkflowRunById(runId, { withNestedWorkflows: false });

      if (existingRun && TERMINAL_RUN_STATUSES.includes(existingRun.status)) {
        throw new HTTPException(409, {
          message:
            `Workflow run ${runId} already finished with status "${existingRun.status}". ` +
            `Use /observe to read its stream back, or stream a new runId.`,
        });
      }

View on GitHub (pinned to 75dd419e61)