mastra-ai/mastra · error · Error
Step ${this.stepNumber} already prepared
Error message
Step ${this.stepNumber} already prepared What it means
Each ObservationStep is single-use: prepare() runs the observation pipeline (threshold checks, buffering, system message building) exactly once. A second prepare() call on the same step would double-apply those effects, so it throws.
Source
Thrown at packages/memory/src/processors/observational-memory/observation-turn/step.ts:66
toJSON() {
return { stepNumber: this.stepNumber, prepared: this._prepared };
}
/** Step context from prepare(). Throws if prepare() hasn't been called. */
get context(): StepContext {
if (!this._context) throw new Error('Step not prepared yet — call prepare() first');
return this._context;
}
/**
* Prepare this step for agent generation.
*
* For step 0: activates buffered chunks, checks reflection, builds system message, filters observed.
* For step > 0: checks thresholds, triggers buffer/observe, saves previous messages,
* builds system message, filters observed.
*/
async prepare(): Promise<StepContext> {
if (this._prepared) throw new Error(`Step ${this.stepNumber} already prepared`);
const { threadId, resourceId, messageList } = this.turn;
// Cast to any for internal access to private OM methods (Turn/Step are internal consumers)
const om = this.turn.om;
let activated = false;
let observed = false;
let buffered = false;
let reflected = false;
let didThresholdCleanup = false;
let observerExchange: StepContext['observerExchange'];
// ── Step 0: Activate buffered chunks ──────────────────────
if (this.stepNumber === 0) {
const step0Messages = getObservableMessages(messageList);
const activation = await om.activate({
threadId,
resourceId,
checkThreshold: true,View on GitHub (pinned to 75dd419e61)
Solutions
- Create a new step via `turn.step(n)` (with an incremented step number) and prepare that instead
- Restructure retry logic so retries happen inside/after a single prepare, not by re-calling prepare
- Track preparation with step.toJSON().prepared before calling prepare()
Example fix
// before await step.prepare(); await step.prepare(); // throws // after await step.prepare(); const next = turn.step(step.stepNumber + 1); await next.prepare();
Defensive patterns
Strategy: type-guard
Validate before calling
if (!step.toJSON().prepared) {
await step.prepare();
} Type guard
function canPrepare(step: ObservationStep): boolean {
return !step.toJSON().prepared;
} Try / catch
try {
await step.prepare();
} catch (e) {
if (e instanceof Error && /already prepared/.test(e.message)) {
// reuse existing context instead of re-preparing
} else throw e;
} Prevention
- Call prepare() exactly once per step instance
- For retries, create a new step with turn.step(n+1) instead of re-preparing
- Check toJSON().prepared before invoking prepare
When it happens
Trigger: Calling `await step.prepare()` twice on the same ObservationStep instance — e.g. a retry wrapper that re-invokes prepare after a partial failure, or code that re-enters generation for the same step number.
Common situations: Retry logic around agent generation that re-calls prepare instead of creating a new step; double invocation from both a hook and the main loop; accidental loop reuse of a step variable.
Related errors
- Turn already started
- Step not prepared yet — call prepare() first
- Turn not started — call start() first
- MastraAuthBetterAuth is not initialized — init() must run fi
- Shared browser not launched. Call createSharedSession() firs
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/2a341c5958d6ca88.
Report an issue: GitHub.