{"record":{"id":"ff5217774b67bd73","repo":"mastra-ai/mastra","slug":"workflow-run-runid-already-finished-with-status","errorCode":null,"errorMessage":"Workflow run ${runId} already finished with status \"${existingRun.status}\". Use /observe to read its stream back, or stream a new runId.","messagePattern":"Workflow run (.+?) already finished with status \"(.+?)\"\\. Use /observe to read its stream back, or stream a new runId\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"packages/server/src/server/handlers/workflows.ts","lineNumber":584,"sourceCode":"\n      if (!workflowId) {\n        throw new HTTPException(400, { message: 'Workflow ID is required' });\n      }\n\n      if (!runId) {\n        throw new HTTPException(400, { message: 'runId required to stream workflow' });\n      }\n\n      const { workflow } = await listWorkflowsFromSystem({ mastra, workflowId });\n\n      if (!workflow) {\n        throw new HTTPException(404, { message: 'Workflow not found' });\n      }\n\n      const existingRun = await workflow.getWorkflowRunById(runId, { withNestedWorkflows: false });\n\n      if (existingRun && TERMINAL_RUN_STATUSES.includes(existingRun.status)) {\n        throw new HTTPException(409, {\n          message:\n            `Workflow run ${runId} already finished with status \"${existingRun.status}\". ` +\n            `Use /observe to read its stream back, or stream a new runId.`,\n        });\n      }\n\n      const serverCache = mastra.getServerCache();\n\n      const run = await workflow.createRun({ runId, resourceId: effectiveResourceId });\n      const result = run.stream({ ...params, requestContext });\n\n      if (serverCache) {\n        return cacheRunStream({ cache: serverCache, runId, source: result.fullStream });\n      }\n\n      return result.fullStream;\n    } catch (error) {\n      return handleError(error, 'Error streaming workflow');","sourceCodeStart":566,"sourceCodeEnd":602,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/workflows.ts#L566-L602","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait workflow.stream({ runId }); // 409 if run finished\n// after\nconst run = await workflow.getWorkflowRunById(runId);\nif (TERMINAL_RUN_STATUSES.includes(run.status)) {\n  await workflow.observeStream({ runId });\n} else {\n  await workflow.stream({ runId });\n}","handlingStrategy":"try-catch","validationCode":"const run = await workflow.getWorkflowRunById(runId);\nconst TERMINAL = ['completed', 'failed', 'canceled'];\nif (run && TERMINAL.includes(run.status)) {\n  // use observe/replay instead of stream\n  await workflow.observeStream({ runId });\n} else {\n  await workflow.stream({ runId });\n}","typeGuard":"const TERMINAL_RUN_STATUSES = ['completed', 'failed', 'canceled'] as const;\ntype RunStatus = 'running' | 'waiting' | ... | typeof TERMINAL_RUN_STATUSES[number];\nfunction isTerminal(status: RunStatus): boolean {\n  return (TERMINAL_RUN_STATUSES as readonly string[]).includes(status);\n}","tryCatchPattern":"try {\n  await workflow.stream({ runId });\n} catch (e) {\n  if (e instanceof MastraClientError && e.status === 409) {\n    await workflow.observeStream({ runId }); // replay finished stream\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["http-409","conflict","workflow-run","streaming"],"backgroundTag":"run-already-finished","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}