{"record":{"id":"d07812638cd15c71","repo":"mastra-ai/mastra","slug":"agentthreadleaseconflicterror-runid-owner","errorCode":null,"errorMessage":"AgentThreadLeaseConflictError(runId, owner)","messagePattern":"AgentThreadLeaseConflictError\\(runId, owner\\)","errorType":"exception","errorClass":"AgentThreadLeaseConflictError","httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/thread-stream-runtime.ts","lineNumber":1333,"sourceCode":"    threadId: string,\n    resourceId: string | undefined,\n    registrationOptions: AgentThreadStrictRegistrationOptions,\n  ): Promise<AgentThreadRunRegistration> {\n    await registrationOptions.validate?.();\n\n    const state = this.#getState(pubsub);\n    this.#sweepStaleSuspendedRecords(state, pubsub);\n    const key = this.#threadKey(resourceId, threadId);\n    const activeRunId = state.activeThreadRunIds.get(key);\n    const activeRecord = activeRunId ? state.threadRunsById.get(activeRunId) : undefined;\n    if (activeRecord && activeRecord.runId !== output.runId && this.#isThreadBlockingRun(state, activeRecord)) {\n      throw new Error(`Cannot register run ${output.runId}: thread is already active with run ${activeRunId}`);\n    }\n    const resolvedPubSub = this.#getPubSub(pubsub);\n    const leaseProvider = this.#getLeaseProvider(resolvedPubSub);\n    const lease = await leaseProvider.acquireLease(key, output.runId, AGENT_THREAD_LEASE_TTL_MS);\n    if (!lease.acquired) {\n      throw new AgentThreadLeaseConflictError(output.runId, lease.owner ?? 'another owner');\n    }\n\n    // A failed external-ownership validation means another recovery attempt may\n    // already own this same runId. Do not release its indistinguishable thread\n    // lease; its TTL or the successor registration will take over renewal.\n    await registrationOptions.validate?.();\n\n    this.#startLeaseRenewal(resolvedPubSub, key, output.runId);\n    const { streamId, streamSeq } = this.#nextStreamIdentity(state, output.runId);\n    const {\n      output: outputForSubscribers,\n      createSubscriberStream,\n      startBroadcast,\n      cancelBroadcast,\n      broadcastFinished,\n    } = this.#withBroadcastStream(output, pubsub, key, streamId);\n    const record: AgentThreadRunRecord<OUTPUT> = {\n      agent,","sourceCodeStart":1315,"sourceCodeEnd":1351,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent/thread-stream-runtime.ts#L1315-L1351","documentation":"After the in-process active-run check, the runtime acquires a distributed lease on the thread key via a lease provider. If the lease could not be acquired, another owner (run) already holds it, and AgentThreadLeaseConflictError is thrown with the conflicting runId and current owner. This guards thread exclusivity across processes/instances.","triggerScenarios":"Two processes/instances (or two runs) attempting to stream the same thread concurrently: leaseProvider.acquireLease returns acquired=false because another run owns the lease within AGENT_THREAD_LEASE_TTL_MS.","commonSituations":"Horizontal scaling with multiple server instances hitting the same thread; a previous run's lease not yet expired after a crash (must wait for TTL); clock/redis misconfiguration making leases behave unexpectedly; replayed requests.","solutions":["Retry the run after the current lease expires (lease TTL is AGENT_THREAD_LEASE_TTL_MS); catch AgentThreadLeaseConflictError and back off.","Serialize per-thread work through a queue so only one run targets a thread at a time.","Ensure the owner releases its lease on cancellation/completion so new runs don't wait for TTL.","Check your lease provider setup (e.g. shared Redis/DB) — instances must point at the same lease store.","Route requests for the same thread to the same instance (sticky routing) to reduce conflicts."],"exampleFix":"// before\nawait runtime.registerRun(output); // throws if lease held\n// after\ntry {\n  await runtime.registerRun(output);\n} catch (e) {\n  if (e instanceof AgentThreadLeaseConflictError) await waitForLease(e.owner);\n  else throw e;\n}","handlingStrategy":"retry","validationCode":"// check whether the lease is currently held before attempting\nconst lease = await leaseProvider.acquireLease(threadKey, runId, TTL);\nif (!lease.acquired) console.warn(`thread busy, owned by ${lease.owner}`);","typeGuard":null,"tryCatchPattern":"try {\n  runtime.registerRun(output);\n} catch (e) {\n  if (e instanceof AgentThreadLeaseConflictError) {\n    await delay(backoff);\n    return registerWithRetry(output); // bounded retries until lease TTL expires\n  }\n  throw e;\n}","preventionTips":["Implement bounded exponential backoff retries around run registration","Ensure all instances share the same lease provider configuration","Release leases promptly on cancellation/completion","Use sticky routing per thread to avoid cross-instance contention","Keep lease TTLs appropriate for expected run durations"],"tags":["concurrency","distributed-lock","lease","threads"],"backgroundTag":"lease-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}