{"record":{"id":"8151c7d229155af8","repo":"JuliusBrussee/caveman","slug":"invalid-mcp-lock-owner","errorCode":null,"errorMessage":"invalid MCP lock owner","messagePattern":"invalid MCP lock owner","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":7783,"sourceCode":"      try {\n        const lockStat = lstatSync(lock);\n        const ownerPath = join(lock, \"owner.json\");\n        const ownerStat = lstatSync(ownerPath);\n        const existing = JSON.parse(readFileSync(ownerPath, \"utf8\")) as Record<string, unknown>;\n        const keys = [\"config_path\", \"pid\", \"schema_version\", \"started_at\", \"token\"];\n        if (!lockStat.isDirectory() || lockStat.isSymbolicLink()\n          || !ownerStat.isFile() || ownerStat.isSymbolicLink()\n          || readdirSync(lock).sort().join(\"\\0\") !== \"owner.json\"\n          || Object.keys(existing).sort().join(\"\\0\") !== keys.join(\"\\0\")\n          || existing.schema_version !== 1\n          || typeof existing.pid !== \"number\" || !Number.isInteger(existing.pid) || existing.pid <= 1\n          || typeof existing.token !== \"string\"\n          || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(existing.token)\n          || existing.config_path !== canonicalPath\n          || typeof existing.started_at !== \"string\"\n          || new Date(existing.started_at).toISOString() !== existing.started_at\n          || process.platform !== \"win32\" && ((lockStat.mode | ownerStat.mode) & 0o077) !== 0) {\n          throw new Error(\"invalid MCP lock owner\");\n        }\n        try { process.kill(existing.pid, 0); }\n        catch (probeError) { stale = (probeError as NodeJS.ErrnoException).code === \"ESRCH\"; }\n      } catch {\n        // Populated claim is durable before publication, so malformed or\n        // ownerless lock can never be our crash residue. Never delete it.\n        stale = false;\n      }\n      if (!stale) throw new Error(`MCP config change already running for ${canonicalPath}`);\n      const quarantine = `${lock}.stale-${token}`;\n      try {\n        renameSync(lock, quarantine);\n        renameSync(claim, lock);\n        fsyncParentDirectory(lock);\n        process.stderr.write(`${mark(\"warn\")} reclaimed stale MCP config lock for ${canonicalPath}\\n`);\n      } catch {\n        throw new Error(`MCP config change already running for ${canonicalPath}`);\n      } finally {","sourceCodeStart":7765,"sourceCodeEnd":7801,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L7765-L7801","documentation":"Before reusing or probing an existing MCP update lock, the code validates the lock owner record: the token must be a UUIDv4 string, config_path must match the canonical path, started_at must be a valid ISO timestamp, and on non-Windows platforms neither the lock file nor the owner file may be group/other accessible. Any violation means the lock is not a legitimate owner record, so it throws instead of trusting or deleting it.","triggerScenarios":"Encountering a lock file at the MCP lock path whose contents are malformed, whose config_path differs from the canonical path being updated, whose started_at is not a round-trippable ISO string, whose token is not a UUIDv4, or whose file mode is too permissive (world/group readable on POSIX).","commonSituations":"A crashed or buggy older CLI version wrote a lock in an unrecognized format; a user hand-edited or truncated the lock file; files were copied between machines with permissions changed; a different project's lock file sits at a shared path.","solutions":["Verify no MCP update is actually running, then remove the malformed lock file and retry the update.","Check the lock file mode on POSIX (chmod 600 lock and owner files) if permissions are the violation.","Ensure you are operating on the same canonical config path the lock was created for.","Upgrade/reinstall the CLI if a stale lock format from an older version is the cause."],"exampleFix":"// before: blindly trusting a hand-edited lock\nconst existing = JSON.parse(readFileSync(lockPath));\nprobe(existing.pid);\n\n// after: validate owner record first\nif (!isUuidV4(existing.token) || existing.config_path !== canonicalPath) {\n  rmSync(lockPath); // only after confirming no live owner\n}","handlingStrategy":"validation","validationCode":"function isValidLockOwner(existing) {\n  const uuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;\n  return existing\n    && typeof existing.token === 'string' && uuidV4.test(existing.token)\n    && existing.config_path === canonicalPath\n    && typeof existing.started_at === 'string'\n    && new Date(existing.started_at).toISOString() === existing.started_at;\n}\n// call before attempting to reuse or probe the lock","typeGuard":"function isWellFormedLock(v: unknown): v is { token: string; config_path: string; started_at: string; pid: number } {\n  return typeof v === 'object' && v !== null\n    && typeof (v as any).token === 'string'\n    && typeof (v as any).config_path === 'string'\n    && typeof (v as any).started_at === 'string'\n    && typeof (v as any).pid === 'number';\n}","tryCatchPattern":"try {\n  acquireOrReuseMcpLock(lockPath, canonicalPath);\n} catch (err) {\n  if (err.message === 'invalid MCP lock owner') {\n    if (!isMcpUpdateRunning()) rmSync(lockPath); // clear malformed lock, then retry\n  } else throw err;\n}","preventionTips":["Never hand-edit or truncate MCP lock files.","Ensure lock files are chmod 600 on POSIX so mode checks pass.","Upgrade the CLI if locks were written by an older version with a different format.","Confirm the update targets the same canonical config path the lock was created for.","Only delete a malformed lock after confirming no update process is alive."],"tags":["mcp","locking","concurrency","lock-validation"],"backgroundTag":"invalid-lock-owner","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}