mastra-ai/mastra · error · HTTPException
Workflow run ${runId} already finished with status "${existi
Error message
Workflow run ${runId} already finished with status "${existingRun.status}". Use /observe to read its stream back, or stream a new runId. What it means
Thrown when attempting to stream a workflow run whose `existingRun.status` is already in `TERMINAL_RUN_STATUSES` (e.g. completed, failed, canceled). The server returns HTTP 409 Conflict because a finished run cannot be streamed live. The message directs you to the /observe endpoint to replay the stored stream.
Source
Thrown at packages/server/src/server/handlers/workflows.ts:584
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.`,
});
}
const serverCache = mastra.getServerCache();
const run = await workflow.createRun({ runId, resourceId: effectiveResourceId });
const result = run.stream({ ...params, requestContext });
if (serverCache) {
return cacheRunStream({ cache: serverCache, runId, source: result.fullStream });
}
return result.fullStream;
} catch (error) {
return handleError(error, 'Error streaming workflow');View on GitHub (pinned to 75dd419e61)
Solutions
- Switch to the /observe endpoint to read the finished run's stream back instead of streaming live.
- Check the run status first (via getWorkflowRunById or the runs API) and only call stream for non-terminal statuses.
- Stream a new runId by starting a fresh run if you need live events again.
Example fix
// before
await workflow.stream({ runId }); // 409 if run finished
// after
const run = await workflow.getWorkflowRunById(runId);
if (TERMINAL_RUN_STATUSES.includes(run.status)) {
await workflow.observeStream({ runId });
} else {
await workflow.stream({ runId });
} Defensive patterns
Strategy: try-catch
Validate before calling
const run = await workflow.getWorkflowRunById(runId);
const TERMINAL = ['completed', 'failed', 'canceled'];
if (run && TERMINAL.includes(run.status)) {
// use observe/replay instead of stream
await workflow.observeStream({ runId });
} else {
await workflow.stream({ runId });
} Type guard
const TERMINAL_RUN_STATUSES = ['completed', 'failed', 'canceled'] as const;
type RunStatus = 'running' | 'waiting' | ... | typeof TERMINAL_RUN_STATUSES[number];
function isTerminal(status: RunStatus): boolean {
return (TERMINAL_RUN_STATUSES as readonly string[]).includes(status);
} Try / catch
try {
await workflow.stream({ runId });
} catch (e) {
if (e instanceof MastraClientError && e.status === 409) {
await workflow.observeStream({ runId }); // replay finished stream
return;
}
throw e;
} Prevention
- Always check run status before opening a live stream.
- Design reconnect logic to fall back from stream to observe for terminal runs.
- Persist run status alongside runId in your app state.
- Use one shared TERMINAL_RUN_STATUSES constant consistent with the server.
When it happens
Trigger: Calling the stream endpoint with a runId whose run already reached a terminal status; retrying a stream subscription after the run finished; resubscribing to a completed run after a page reload.
Common situations: UI reconnect logic blindly re-streams a runId after network loss when the run has since finished; polling loops that keep calling stream instead of switching to observe once the run ends; test scripts that re-run the same stream request.
Related errors
- Cannot register run ${output.runId}: thread is already activ
- Skill with id "${id}" already exists.
- DATASET_ITEM_IDENTITY_CONFLICT
- Agent with id ${id} already exists
- MCP client with id ${id} already exists
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ff5217774b67bd73.
Report an issue: GitHub.