mastra-ai/mastra · error · ThreadLockError

ThreadLockError(threadId, ownerPid)

Error message

ThreadLockError(threadId, ownerPid)

What it means

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.

Source

Thrown at mastracode/sdk/src/utils/thread-lock.ts:61

}

/**
 * Attempt to acquire a lock for the given thread.
 * Throws ThreadLockError if another live process holds the lock.
 * Reclaims stale locks from dead processes.
 */
export function acquireThreadLock(threadId: string): void {
  const lockPath = getLockPath(threadId);
  const myPid = process.pid;

  // Check for existing lock
  if (fs.existsSync(lockPath)) {
    try {
      const content = fs.readFileSync(lockPath, 'utf-8').trim();
      const ownerPid = parseInt(content, 10);

      if (!isNaN(ownerPid) && ownerPid !== myPid && isProcessAlive(ownerPid)) {
        throw new ThreadLockError(threadId, ownerPid);
      }
      // Stale lock (dead process) or our own lock — reclaim it
    } catch (error) {
      if (error instanceof ThreadLockError) throw error;
      // File read error — try to overwrite
    }
  }

  // Write our PID to the lock file
  fs.writeFileSync(lockPath, String(myPid), { mode: 0o644 });
}

/**
 * Release the lock for the given thread (only if we own it).
 */
export function releaseThreadLock(threadId: string): void {
  const lockPath = getLockPath(threadId);
  const myPid = process.pid;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Identify the owning process (`ps -p <ownerPid>`) and terminate it or close the other session; the lock is then reclaimed automatically on next acquire
  2. If the PID is dead but isProcessAlive misjudges (containers/VMs with different PID namespaces), delete the lock file manually
  3. Avoid running two mastracode sessions against the same thread in parallel

Example fix

// recover from a stale lock whose owner is gone
ps -p 42421 || rm .mastra/locks/<threadId>.lock
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
import { process } from 'node:process';
const lockPath = getLockPath(threadId); // your lock path helper
if (existsSync(lockPath)) {
  const pid = parseInt(readFileSync(lockPath, 'utf-8').trim(), 10);
  if (!isNaN(pid) && pid !== process.pid && isAlive(pid)) {
    throw new Error(`Thread ${threadId} is locked by PID ${pid}; close that session first.`);
  }
}

Type guard

const isLockHeldByLiveProcess = (lockPath: string, myPid: number): boolean => {
  try {
    const pid = parseInt(readFileSync(lockPath, 'utf-8').trim(), 10);
    return !isNaN(pid) && pid !== myPid;
  } catch { return false; }
};

Try / catch

try {
  await acquireThreadLock(threadId);
} catch (e) {
  if (e instanceof ThreadLockError) {
    console.error(`Thread "${e.threadId}" is locked by PID ${e.ownerPid}. Close that session or kill the process.`);
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/7a06695f4a1f3be4. Report an issue: GitHub.