{"record":{"id":"bece110f7e8e4e00","repo":"mastra-ai/mastra","slug":"agent-does-not-support-recover-only-durable-agent","errorCode":null,"errorMessage":"Agent does not support recover. Only durable agents (createDurableAgent) can recover runs.","messagePattern":"Agent does not support recover\\. Only durable agents \\(createDurableAgent\\) can recover runs\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/agents.ts","lineNumber":2922,"sourceCode":"      // correct draft/published version and any downstream agent lookups\n      // (memory, tools) see the same stashed overrides. Mirrors the order\n      // used by other execute-style routes that predate this one but\n      // needed the same fix.\n      mergeBodyRequestContext(serverRequestContext, bodyRequestContext);\n      stashVersionOverrides(serverRequestContext, versions);\n      ensureDefaultVersionStatus(serverRequestContext, versionOptions);\n\n      const agent = await getAgentFromSystem({\n        mastra,\n        agentId,\n        versionOptions,\n      });\n\n      // Durable-agent check via duck-typing to avoid a hard runtime dep on the\n      // DurableAgent class inside @mastra/core (mirrors the pattern used by\n      // Mastra.recoverAllDurableAgents()).\n      if (!isDurableAgentLike(agent)) {\n        throw new HTTPException(400, {\n          message: 'Agent does not support recover. Only durable agents (createDurableAgent) can recover runs.',\n        });\n      }\n\n      const workflowsStore = await mastra.getStorage()?.getStore('workflows');\n      const workflowRun = await workflowsStore?.getWorkflowRunById({\n        workflowName: DurableStepIds.AGENTIC_LOOP,\n        runId,\n      });\n      await validateRunOwnership(workflowRun, getEffectiveResourceId(serverRequestContext, undefined));\n\n      // NOTE: DurableAgent.recover() reads the workflow's requestContext from\n      // the persisted snapshot. serverRequestContext is only used above for\n      // ownership checks and version resolution.\n      const streamResult = await (agent as any).recover(runId, {\n        abortSignal,\n      });\n","sourceCodeStart":2904,"sourceCodeEnd":2940,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/agents.ts#L2904-L2940","documentation":"This HTTP 400 error is thrown by the RECOVER_ROUTE handler (POST /agents/:agentId/recover) when the resolved agent is not a durable agent. Recovery re-drives a persisted agentic-loop workflow snapshot, a capability that only agents created with createDurableAgent implement. The server duck-types the agent via isDurableAgentLike to avoid a hard runtime dependency on the DurableAgent class in @mastra/core; a plain Agent (new Agent(...) / Mastra.getAgent) fails the check.","triggerScenarios":"Calling POST /api/agents/:agentId/recover where `agentId` resolves to a regular (non-durable) Agent; pointing the request at an agent id from a different Mastra instance/version where the same id was registered as a durable agent; calling recover against an agent created with createDurableAgent but fetched through a version/draft resolution that returned a plain Agent instance.","commonSituations":"Teams adopting durable agents who keep recovery scripts written for regular agents; mixed deployments where some agents are durable and others are not and the ops tooling recovers 'all' agents uniformly; agent id collisions between draft and published versions.","solutions":["Create the agent with createDurableAgent (from @mastra/core/durable-agent or the server's durable-agent export) instead of new Agent, so it supports recover.","Only call /agents/:agentId/recover for agents actually registered as durable; filter your recovery list to durable agents (e.g. check isDurableAgentLike(agent) or use Mastra.recoverAllDurableAgents()).","If recovery was attempted for a plain agent run, instead re-run/stream the agent normally — non-durable runs cannot be recovered from a snapshot.","Verify the agentId/version resolution returns the durable registration (check versionOptions/draft overrides) rather than another agent with the same id."],"exampleFix":"// before\nimport { Agent } from '@mastra/core/agent';\nexport const agent = new Agent({ name: 'helper', model, instructions });\n// after\nimport { createDurableAgent } from '@mastra/core/durable-agent';\nexport const agent = createDurableAgent({ name: 'helper', model, instructions });","handlingStrategy":"type-guard","validationCode":"const agent = await mastra.getAgent({ agentId });\nif (!isDurableAgentLike(agent)) {\n  throw new Error(`Agent ${agentId} is not a durable agent; recover is not supported.`);\n}","typeGuard":"function isDurableAgentLike(a: unknown): a is { recover: (runId: string, opts?: unknown) => Promise<unknown> } {\n  return typeof a === 'object' && a !== null && typeof (a as { recover?: unknown }).recover === 'function';\n}","tryCatchPattern":"try {\n  const stream = await fetch(`/api/agents/${agentId}/recover`, { method: 'POST', body: JSON.stringify({ runId }) });\n} catch (e) {\n  if (/does not support recover/i.test(String(e))) {\n    // fall back to re-running the agent normally instead of recovering\n  }\n  throw e;\n}","preventionTips":["Only register agents that must survive restarts as durable agents via createDurableAgent.","Keep an allowlist of durable agent ids in recovery tooling instead of recovering every agent.","Check for a recover() method (isDurableAgentLike) client-side before calling the recover endpoint.","Ensure version/draft resolution points at the durable registration, not a same-id plain Agent."],"tags":["http-400","durable-agent","agents-api","capability-mismatch"],"backgroundTag":"operation-not-supported","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}