mastra-ai/mastra · error · HTTPException
Agent does not support recover. Only durable agents (createD
Error message
Agent does not support recover. Only durable agents (createDurableAgent) can recover runs.
What it means
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.
Source
Thrown at packages/server/src/server/handlers/agents.ts:2922
// correct draft/published version and any downstream agent lookups
// (memory, tools) see the same stashed overrides. Mirrors the order
// used by other execute-style routes that predate this one but
// needed the same fix.
mergeBodyRequestContext(serverRequestContext, bodyRequestContext);
stashVersionOverrides(serverRequestContext, versions);
ensureDefaultVersionStatus(serverRequestContext, versionOptions);
const agent = await getAgentFromSystem({
mastra,
agentId,
versionOptions,
});
// Durable-agent check via duck-typing to avoid a hard runtime dep on the
// DurableAgent class inside @mastra/core (mirrors the pattern used by
// Mastra.recoverAllDurableAgents()).
if (!isDurableAgentLike(agent)) {
throw new HTTPException(400, {
message: 'Agent does not support recover. Only durable agents (createDurableAgent) can recover runs.',
});
}
const workflowsStore = await mastra.getStorage()?.getStore('workflows');
const workflowRun = await workflowsStore?.getWorkflowRunById({
workflowName: DurableStepIds.AGENTIC_LOOP,
runId,
});
await validateRunOwnership(workflowRun, getEffectiveResourceId(serverRequestContext, undefined));
// NOTE: DurableAgent.recover() reads the workflow's requestContext from
// the persisted snapshot. serverRequestContext is only used above for
// ownership checks and version resolution.
const streamResult = await (agent as any).recover(runId, {
abortSignal,
});
View on GitHub (pinned to 75dd419e61)
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.
Example fix
// before
import { Agent } from '@mastra/core/agent';
export const agent = new Agent({ name: 'helper', model, instructions });
// after
import { createDurableAgent } from '@mastra/core/durable-agent';
export const agent = createDurableAgent({ name: 'helper', model, instructions }); Defensive patterns
Strategy: type-guard
Validate before calling
const agent = await mastra.getAgent({ agentId });
if (!isDurableAgentLike(agent)) {
throw new Error(`Agent ${agentId} is not a durable agent; recover is not supported.`);
} Type guard
function isDurableAgentLike(a: unknown): a is { recover: (runId: string, opts?: unknown) => Promise<unknown> } {
return typeof a === 'object' && a !== null && typeof (a as { recover?: unknown }).recover === 'function';
} Try / catch
try {
const stream = await fetch(`/api/agents/${agentId}/recover`, { method: 'POST', body: JSON.stringify({ runId }) });
} catch (e) {
if (/does not support recover/i.test(String(e))) {
// fall back to re-running the agent normally instead of recovering
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bad request: ${responseText}
- DURABLE_AGENT_RECOVER_ALREADY_IN_PROGRESS
- DURABLE_AGENT_RECOVER_LEASE_ACQUIRE_FAILED
- DURABLE_AGENT_RECOVER_SNAPSHOT_NOT_FOUND
- DURABLE_AGENT_RECOVER_INVALID_SNAPSHOT
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/bece110f7e8e4e00.
Report an issue: GitHub.