mastra-ai/mastra · error · Error

Dynamic workflow references step

Error message

Dynamic workflow references step "${id}" which is not registered as an agent or tool on this Mastra instance.

What it means

A 'step' entry in a stored dynamic workflow resolves its id first as an agent, then as a tool, via `createStepFromAgent`/`createStepFromTool`. If the id matches neither, the library throws because stored plain-id step entries can only be backed by registered agents or tools.

Solutions

  1. Register the referenced resource as an agent or tool on the Mastra instance so the id resolves.
  2. If the step is a plain Step, re-serialize the workflow so the step is embedded inline rather than referenced by id.
  3. Fix id mismatches between the stored graph and the registry (typo/rename).
  4. Pre-validate each stored step id with `mastra.getAgent(id) ?? mastra.getTool(id)` before rehydrating.

Example fix

// before: stored { type: 'step', id: 'myStep' } with only a local createStep()
// after: register it as a tool-backed step
const mastra = new Mastra({ tools: { myStep: createToolFromStep(myStep) } });
Defensive patterns

Strategy: validation

Validate before calling

function assertStepIdsResolvable(stored, mastra) {
  for (const e of stored.entries) {
    if (e.type === 'step') {
      const id = typeof e === 'string' ? e : e.id;
      if (id && !mastra.getAgent?.(id) && !mastra.getTool?.(id)) {
        throw new Error(`Step id "${id}" resolves to neither agent nor tool`);
      }
    }
  }
}

Type guard

function stepIdResolves(mastra: Mastra, id: string): boolean {
  return !!mastra.getAgent?.(id) || !!mastra.getTool?.(id);
}

Try / catch

try {
  const wf = rehydrateWorkflow(stored, mastra);
} catch (err) {
  const m = err instanceof Error && err.message.match(/references step "([^"]+)" which is not registered/);
  if (m) throw new Error(`'${m[1]}' must be registered as an agent or tool (plain steps cannot be referenced by id).`);
  throw err;
}

Prevention

When it happens

Trigger: rehydrateWorkflow processes a stored step entry whose id (entry.id or nested id) is not registered as an agent or tool on the Mastra instance — e.g. referencing a plain Step object that was never registered, or an id typo.

Common situations: Assuming arbitrary inline `createStep()` steps can be referenced by id in stored graphs (they must be serialized inline or registered); typos in step ids; renaming agents/tools without updating stored graphs.

Related errors


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

Appendix: source

Thrown at packages/core/src/workflows/dynamic/rehydrate.ts:294

          `Dynamic workflow references tool "${entry.toolId}" which is not registered on this Mastra instance.`,
        );
      }
      return { type: 'tool', id: entry.id, toolId: entry.toolId, tool, options: rebuildToolOptions(entry) };
    }
    case 'step': {
      const { id } = entry.step;
      // Wrap the resolved agent/tool in a real Step (same adapters `createStep`
      // uses) so the entry honors the executeStep contract instead of casting a
      // raw Agent/Tool instance — those don't carry a step-shaped `execute`.
      const agent = tryGetAgentById(mastra, id);
      if (agent) {
        return { type: 'step', step: createStepFromAgent(agent) as unknown as Step };
      }
      const tool = tryGetToolById(mastra, id);
      if (tool) {
        return { type: 'step', step: createStepFromTool(tool as any) as unknown as Step };
      }
      throw new Error(
        `Dynamic workflow references step "${id}" which is not registered as an agent or tool on this Mastra instance.`,
      );
    }
    case 'workflow': {
      const nested = assertWorkflowExists(mastra, entry.workflowId);
      // Same call-site identity rule as top-level nested workflows: run the
      // clone under the declared id so results are keyed the way the portable
      // definition addresses them.
      const step = entry.id && entry.id !== nested.id ? cloneWorkflow(nested as any, { id: entry.id }) : nested;
      return { type: 'step', step: step as unknown as Step };
    }
    case 'mapping':
      throw new Error(
        `mapping entries cannot appear inside .parallel(), .branch(), or .foreach(); they must be top-level.`,
      );
  }
}

View on GitHub (pinned to 75dd419e61)