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

  1. Inspect server logs/traces for the underlying workflow step failure — the true cause precedes this wrapper.
  2. Throw Error objects (not strings/objects) inside tools and steps so message survives into result.error.
  3. Check model provider credentials and network reachability before retrying recover().
  4. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/a24a85fcc4e84666. Report an issue: GitHub.