mastra-ai/mastra · error
Cannot register run ${output.runId}: thread is already activ
Error message
Cannot register run ${output.runId}: thread is already active with run ${activeRunId} What it means
The thread stream runtime allows only one blocking run per (resourceId, threadId) at a time. When registering a new run, if the thread already has an active run (different runId) that is still blocking, registration fails with this error to prevent two runs from concurrently driving the same thread's stream/history.
Source
Thrown at packages/core/src/agent/thread-stream-runtime.ts:1327
async #registerRunStrict<OUTPUT>(
agent: Agent<any, any, any, any>,
output: MastraModelOutput<OUTPUT>,
streamOptions: AgentExecutionOptions<OUTPUT>,
pubsub: PubSub | undefined,
threadId: string,
resourceId: string | undefined,
registrationOptions: AgentThreadStrictRegistrationOptions,
): Promise<AgentThreadRunRegistration> {
await registrationOptions.validate?.();
const state = this.#getState(pubsub);
this.#sweepStaleSuspendedRecords(state, pubsub);
const key = this.#threadKey(resourceId, threadId);
const activeRunId = state.activeThreadRunIds.get(key);
const activeRecord = activeRunId ? state.threadRunsById.get(activeRunId) : undefined;
if (activeRecord && activeRecord.runId !== output.runId && this.#isThreadBlockingRun(state, activeRecord)) {
throw new Error(`Cannot register run ${output.runId}: thread is already active with run ${activeRunId}`);
}
const resolvedPubSub = this.#getPubSub(pubsub);
const leaseProvider = this.#getLeaseProvider(resolvedPubSub);
const lease = await leaseProvider.acquireLease(key, output.runId, AGENT_THREAD_LEASE_TTL_MS);
if (!lease.acquired) {
throw new AgentThreadLeaseConflictError(output.runId, lease.owner ?? 'another owner');
}
// A failed external-ownership validation means another recovery attempt may
// already own this same runId. Do not release its indistinguishable thread
// lease; its TTL or the successor registration will take over renewal.
await registrationOptions.validate?.();
this.#startLeaseRenewal(resolvedPubSub, key, output.runId);
const { streamId, streamSeq } = this.#nextStreamIdentity(state, output.runId);
const {
output: outputForSubscribers,
createSubscriberStream,View on GitHub (pinned to 75dd419e61)
Solutions
- Wait for or cancel/complete the existing run before starting a new one on the same thread.
- Abort the first run's AbortController so its registration is cleaned up, then retry the new stream.
- Fix client-side double submits (disable send button while streaming, debounce, dedupe by request id).
- If the active record is genuinely stale, ensure the sweep logic/lease TTL runs or clear stale thread-run state in your storage.
- Use separate threads for concurrent generations instead of sharing one thread.
Example fix
// before
agent.stream(...) // while previous run on same threadId still active
// after
await previousRun; // or previousRun.abort()
await agent.stream({ ..., threadId }); Defensive patterns
Strategy: try-catch
Validate before calling
// before starting a run, check locally whether the thread is already streaming
if (activeStreamsByThread.has(threadId)) {
throw new Error(`thread ${threadId} already has an active run`);
} Try / catch
try {
runtime.registerRun(output);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Cannot register run')) {
// wait/cancel the prior run, then retry once
await cancelActiveRun(threadId);
await runtime.registerRun(output);
} else throw e;
} Prevention
- Disable/queue chat sends while a run on the same thread is streaming
- Always abort runs via AbortController when the user navigates away or cancels
- Dedupe retries with idempotency/request ids
- Use distinct threads for concurrent generations
- Monitor for stale active-run records and ensure sweeps/leases expire
When it happens
Trigger: Starting a second agent stream/generation on the same thread while a previous blocking run is still active — e.g. double-submitting a chat prompt without cancelling the first request, or a retried request whose original run hasn't been reaped by #sweepStaleSuspendedRecords.
Common situations: UI allows parallel sends; server retries duplicate an in-flight request; a crashed run left a stale active record that hasn't been swept yet; multi-instance deployments sharing state with a slow/stuck first run.
Related errors
- AgentThreadLeaseConflictError(runId, owner)
- Cannot start an overlapping WebSocket Responses continuation
- Workflow run ${runId} already finished with status "${existi
- UI Messages require a data property when using data- prefixe
- UI Messages require a data property when using data- prefixe
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/25b7e141149d181a.
Report an issue: GitHub.