{"record":{"id":"7a06695f4a1f3be4","repo":"mastra-ai/mastra","slug":"threadlockerror-threadid-ownerpid","errorCode":null,"errorMessage":"ThreadLockError(threadId, ownerPid)","messagePattern":"ThreadLockError\\(threadId, ownerPid\\)","errorType":"exception","errorClass":"ThreadLockError","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/utils/thread-lock.ts","lineNumber":61,"sourceCode":"}\n\n/**\n * Attempt to acquire a lock for the given thread.\n * Throws ThreadLockError if another live process holds the lock.\n * Reclaims stale locks from dead processes.\n */\nexport function acquireThreadLock(threadId: string): void {\n  const lockPath = getLockPath(threadId);\n  const myPid = process.pid;\n\n  // Check for existing lock\n  if (fs.existsSync(lockPath)) {\n    try {\n      const content = fs.readFileSync(lockPath, 'utf-8').trim();\n      const ownerPid = parseInt(content, 10);\n\n      if (!isNaN(ownerPid) && ownerPid !== myPid && isProcessAlive(ownerPid)) {\n        throw new ThreadLockError(threadId, ownerPid);\n      }\n      // Stale lock (dead process) or our own lock — reclaim it\n    } catch (error) {\n      if (error instanceof ThreadLockError) throw error;\n      // File read error — try to overwrite\n    }\n  }\n\n  // Write our PID to the lock file\n  fs.writeFileSync(lockPath, String(myPid), { mode: 0o644 });\n}\n\n/**\n * Release the lock for the given thread (only if we own it).\n */\nexport function releaseThreadLock(threadId: string): void {\n  const lockPath = getLockPath(threadId);\n  const myPid = process.pid;","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/utils/thread-lock.ts#L43-L79","documentation":"ThreadLockError is thrown by acquireThreadLock when a lock file for the thread already exists and the PID inside it belongs to another process that is still alive. This prevents two sessions from mutating the same thread concurrently. Stale locks (dead owner) or the caller's own PID are safely reclaimed instead.","triggerScenarios":"Two mastracode processes try to acquire the lock for the same threadId; the lock file at the thread's lock path contains a live foreign PID (verified with isProcessAlive).","commonSituations":"The same project/thread opened in two terminals; a previously 'killed' session that actually survived (detached child, background job) still holds the lock; PID reuse coincidentally matching a stale lock file's PID.","solutions":["Identify the owning process (`ps -p <ownerPid>`) and terminate it or close the other session; the lock is then reclaimed automatically on next acquire","If the PID is dead but isProcessAlive misjudges (containers/VMs with different PID namespaces), delete the lock file manually","Avoid running two mastracode sessions against the same thread in parallel"],"exampleFix":"// recover from a stale lock whose owner is gone\nps -p 42421 || rm .mastra/locks/<threadId>.lock","handlingStrategy":"try-catch","validationCode":"import { existsSync, readFileSync } from 'node:fs';\nimport { process } from 'node:process';\nconst lockPath = getLockPath(threadId); // your lock path helper\nif (existsSync(lockPath)) {\n  const pid = parseInt(readFileSync(lockPath, 'utf-8').trim(), 10);\n  if (!isNaN(pid) && pid !== process.pid && isAlive(pid)) {\n    throw new Error(`Thread ${threadId} is locked by PID ${pid}; close that session first.`);\n  }\n}","typeGuard":"const isLockHeldByLiveProcess = (lockPath: string, myPid: number): boolean => {\n  try {\n    const pid = parseInt(readFileSync(lockPath, 'utf-8').trim(), 10);\n    return !isNaN(pid) && pid !== myPid;\n  } catch { return false; }\n};","tryCatchPattern":"try {\n  await acquireThreadLock(threadId);\n} catch (e) {\n  if (e instanceof ThreadLockError) {\n    console.error(`Thread \"${e.threadId}\" is locked by PID ${e.ownerPid}. Close that session or kill the process.`);\n  } else throw e;\n}","preventionTips":["Don't run two mastracode sessions against the same thread in parallel","If a PID is reported dead but the lock persists, remove the lock file manually","Check `ps -p <ownerPid>` before deleting a lock to avoid stealing a live session's lock","Be careful with PID namespaces in containers when judging staleness"],"tags":["concurrency","locking","process","pid"],"backgroundTag":"lock-already-held","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}