mastra-ai/mastra · error · Error
(result as any).error?.message || 'Workflow recover failed'
Error message
(result as any).error?.message || 'Workflow recover failed'
What it means
After a recover() workflow attempt reaches a terminal (non-suspended) status, snapshots are deleted; if that status is 'failed', the raw workflow error message is surfaced via `throw new Error((result as any).error?.message || 'Workflow recover failed')` at durable-agent.ts:2475. The fallback text appears when the workflow result carries status 'failed' but no readable error.message.
Source
Thrown at packages/core/src/agent/durable/durable-agent.ts:2475
);
recoveryLease.assertOwned();
const result = await this.#raceRecoveryLease(
run.restart({
requestContext,
...createObservabilityContext({ currentSpan: recoverAgentSpan }),
} as any),
recoveryLease,
);
recoveryLease.assertOwned();
// Snapshot cleanup runs for every non-suspended terminal (success or
// failed) so storage stays bounded — mirrors the start()/resume()
// contract.
if (result?.status && result.status !== 'suspended') {
await this.deleteRunSnapshots(runId);
recoveryLease.assertOwned();
}
if (result?.status === 'failed') {
throw new Error((result as any).error?.message || 'Workflow recover failed');
}
})
.catch(async error => {
const leaseLossError = recoveryLease.getLossError();
if (leaseLossError) {
await threadRegistration?.rollback({ releaseLease: false });
streamCleanup();
cleanupOwnedRegistryState();
}
const recoveryError = leaseLossError ?? error;
const reported = await this.#reportRecoveryFailure(runId, recoveryError);
if (!reported && !leaseLossError) {
await threadRegistration?.rollback();
streamCleanup();
cleanupOwnedRegistryState();
}
throw recoveryError;
})View on GitHub (pinned to 75dd419e61)
Solutions
- Inspect server logs/traces for the underlying workflow step failure — the true cause precedes this wrapper.
- Throw Error objects (not strings/objects) inside tools and steps so message survives into result.error.
- Check model provider credentials and network reachability before retrying recover().
- Retry recover() once the underlying cause is fixed; snapshots were deleted only for finished terminal runs, so verify run state first.
Example fix
// before
throw 'rate limit exceeded'; // inside a tool -> opaque 'Workflow recover failed'
// after
throw new Error('rate limit exceeded'); Defensive patterns
Strategy: try-catch
Try / catch
try {
await agent.recover(runId);
} catch (e) {
if (String(e) === 'Workflow recover failed') {
// underlying step error was lost: check workflow traces/logs for root cause
} else {
// e.message carries the real workflow failure
logger.error('recover failed', { cause: (e as Error).message });
}
} Prevention
- Throw Error instances (not strings) inside tools and workflow steps.
- Monitor provider credentials/health before invoking recover().
- Inspect traces on the durable agentic-loop workflow for the root failure.
When it happens
Trigger: The recovered workflow run fails during resume (model call error, step throw, abort handling) and result.error is undefined or lacks .message; a step inside the durable agentic loop throws a non-Error value so no message survives serialization.
Common situations: LLM provider outage or auth failure during recovery; a tool throwing a string instead of an Error; snapshot rehydration succeeding but execution failing immediately after; hitting this after a crash-recovery when underlying credentials expired.
Related errors
- DURABLE_AGENT_RECOVER_NO_MASTRA
- DURABLE_AGENT_RECOVER_NO_STORAGE
- Could not create the session: ${message}. Try again.
- DURABLE_AGENT_RECOVER_ALREADY_IN_PROGRESS
- DURABLE_AGENT_RECOVER_LEASE_ACQUIRE_FAILED
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a24a85fcc4e84666.
Report an issue: GitHub.