mastra-ai/mastra · error · Error
Turn already started
Error message
Turn already started
What it means
A Turn models one user turn and start() may only run once, since it fetches/caches the record and captures the generation count baseline. A second start() on the same instance would corrupt that baseline, so it throws 'Turn already started'.
Source
Thrown at packages/memory/src/processors/observational-memory/observation-turn/turn.ts:141
/** The current step, if one exists. */
get currentStep(): ObservationStep | undefined {
return this._currentStep;
}
addHooks(hooks?: ObservationTurnHooks): void {
if (!hooks) return;
Object.assign(this.hooks, hooks);
}
/**
* Load context and cache the record. Call once at the start of the turn.
*
* If a MemoryContextProvider is passed, loads historical messages and adds
* them to the MessageList. Without a provider, only fetches/caches the record.
*/
async start(memory?: MemoryContextProvider, runState?: MemoryRunState): Promise<TurnContext> {
if (this._started) throw new Error('Turn already started');
this._started = true;
this._record = await this.om.getOrCreateRecord(this.threadId, this.resourceId);
runState?.set(`observational-memory:record:${this.threadId}:${this.resourceId ?? ''}`, this._record);
this._generationCountAtStart = this._record.generationCount;
this.memory = memory;
if (memory) {
const ctx = await loadMemoryContextMessages({
memory,
messageList: this.messageList,
threadId: this.threadId,
resourceId: this.resourceId,
runState,
});
this._context = {
messages: ctx.messages,View on GitHub (pinned to 75dd419e61)
Solutions
- Create a fresh ObservationTurn for a new start cycle rather than reusing the instance
- Ensure only one code path calls start() for a given turn instance
- Wrap start() so failures happen before side effects, and on retry construct a new turn
Example fix
// before await turn.start(); await turn.start(memory); // throws // after await turn.start(memory); // for a retry: const retryTurn = new ObservationTurn(om, threadId, resourceId, messageList); await retryTurn.start(memory);
Defensive patterns
Strategy: try-catch
Validate before calling
// call start() exactly once per turn instance; track it if (!turnStartPromise) turnStartPromise = turn.start(memory, runState); await turnStartPromise;
Type guard
// Memoize the start promise so double invocation is impossible
let started: Promise<TurnContext> | null = null;
function ensureStarted(t: ObservationTurn): Promise<TurnContext> {
return started ??= t.start();
} Try / catch
try {
await turn.start(memory, runState);
} catch (e) {
if (e instanceof Error && e.message === 'Turn already started') {
// ignore: another path already started this turn
} else throw e;
} Prevention
- Memoize the start() promise per turn instance
- Ensure only one code path (loop or hook) starts the turn
- For retries, construct a new ObservationTurn rather than restarting
When it happens
Trigger: Calling `await turn.start()` a second time on the same ObservationTurn — e.g. duplicate start calls from middleware and the main loop, or a retry wrapper that re-invokes start after a failure.
Common situations: Retry logic that re-calls start() instead of creating a new turn; double-wiring of memory hooks both calling start; framework version change where the caller now also starts the turn.
Related errors
- Step ${this.stepNumber} already prepared
- 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/72badbf337c21202.
Report an issue: GitHub.