mastra-ai/mastra · error
Experiment emitted multiple terminal semantic events
Error message
Experiment emitted multiple terminal semantic events
What it means
During an experiment run, the CLI processes semantic events from the experiment stream and expects exactly one terminal event ('experiment.run.finished'). This error is thrown when a second terminal event arrives after one has already been recorded, indicating the experiment runtime emitted a malformed or duplicated lifecycle stream.
Source
Thrown at packages/cli/src/commands/experiment/runtime.ts:581
tenant: packet.tenant,
environment: packet.environment,
artifacts: packet.artifacts,
scorerVersions: Object.fromEntries(packet.scorers.map(scorer => [scorer.id, scorer.version])),
policies: packet.policies,
requestedAt: packet.requestedAt,
},
persistence: { experiments: 'none', scores: 'none' },
experimentId: request.experimentId,
onEvent: async event => {
if (event.type === 'experiment.run.started') await writeEvent('run-started', { semanticEvent: event });
else if (event.type === 'experiment.item.completed') {
await writeEvent('item-completed', {
itemId: event.itemId,
itemIndex: event.itemIndex,
semanticEvent: event,
});
} else if (event.type === 'experiment.run.finished') {
if (finishedEvent) throw new Error('Experiment emitted multiple terminal semantic events');
finishedEvent = event;
}
},
}),
deadlinePromise,
]);
} catch (error) {
runErrorRetryable = isRecord(error) && error.retryable === true;
runError = error instanceof Error ? error : new Error(String(error));
} finally {
clearDeadlineTimer?.();
clearDeadlineTimer = undefined;
}
try {
let clearTimer: (() => void) | undefined;
await Promise.race([
mastra.shutdown(),
new Promise((_, reject) => {View on GitHub (pinned to 75dd419e61)
Solutions
- Inspect the experiment runner for duplicate emission of 'experiment.run.finished' (e.g. both a normal-completion path and a deadline path emitting the event).
- Guard the deadline path so it does not emit a terminal event when the run already finished, or abort the stream first.
- If events may be redelivered by the transport, make the handler idempotent (ignore or log subsequent terminal events instead of throwing) if duplicate tolerance is acceptable.
- Update to the latest @mastra/cli / core versions in case this is a known runtime duplication bug.
Example fix
// before
} else if (event.type === 'experiment.run.finished') {
if (finishedEvent) throw new Error('Experiment emitted multiple terminal semantic events');
finishedEvent = event;
}
// after
} else if (event.type === 'experiment.run.finished') {
if (finishedEvent) {
logger.warn('Ignoring duplicate terminal semantic event');
return;
}
finishedEvent = event;
} Defensive patterns
Strategy: try-catch
Try / catch
try {
await executeRun(...);
} catch (err) {
if (err.message === 'Experiment emitted multiple terminal semantic events') {
// treat last terminal event as authoritative, or log and re-run with dedup
}
} Prevention
- Make terminal-event handling idempotent in custom runners.
- Ensure deadline/timeout paths cannot emit after normal completion.
- Add stream-level event deduplication before the handler.
When it happens
Trigger: Calling executeRun with a run whose event stream emits two 'experiment.run.finished' events (e.g. a race between the run completing and the deadline/timeout path also finalizing, or a runtime bug emitting duplicate finish events).
Common situations: Running experiments against a runtime that redelivers events, deadlines firing concurrently with normal completion, or a runner that retries and finishes the run twice without a new stream.
Related errors
- Response body is null
- Response body is null
- No response body for log stream
- 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/515050cd53e0c524.
Report an issue: GitHub.