{"record":{"id":"84af4948e4b553ef","repo":"affaan-m/ECC","slug":"refusing-to-release-a-changed-claude-settings-lock-lockpath","errorCode":null,"errorMessage":"Refusing to release a changed Claude settings lock: ${lockPath}","messagePattern":"Refusing to release a changed Claude settings lock: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/claude-settings-lock.js","lineNumber":53,"sourceCode":"    fs.closeSync(descriptor);\n    descriptor = undefined;\n    fs.linkSync(tempPath, lockPath);\n  } catch (error) {\n    if (descriptor !== undefined) fs.closeSync(descriptor);\n    fs.rmSync(tempPath, { force: true });\n    throw error;\n  }\n  fs.rmSync(tempPath, { force: true });\n\n  let released = false;\n  return () => {\n    if (released) return;\n    const quarantinePath = `${lockPath}.release-${process.pid}-${crypto.randomBytes(8).toString('hex')}`;\n    fs.renameSync(lockPath, quarantinePath);\n    const quarantinedStats = fs.lstatSync(quarantinePath, { bigint: true });\n    if (!sameFileIdentity(quarantinedStats, ownedStats)) {\n      if (!fs.existsSync(lockPath)) fs.renameSync(quarantinePath, lockPath);\n      throw new Error(`Refusing to release a changed Claude settings lock: ${lockPath}`);\n    }\n    released = true;\n    fs.rmSync(quarantinePath, { force: true });\n  };\n}\n\nfunction inspectSettingsLock(lockPath) {\n  const descriptor = fs.openSync(lockPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));\n  try {\n    const stats = fs.fstatSync(descriptor, { bigint: true });\n    const pathStats = fs.lstatSync(lockPath, { bigint: true });\n    if (\n      !stats.isFile()\n      || pathStats.isSymbolicLink()\n      || !pathStats.isFile()\n      || !sameFileIdentity(stats, pathStats)\n    ) {\n      return { metadata: null, stats };","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/claude-settings-lock.js#L35-L71","documentation":"When releasing the Claude settings lock, createSettingsLock quarantines the lock file by renaming it, then verifies the quarantined file's identity (inode/device via bigint lstat) matches the file this process originally locked. If the identity changed — someone replaced or recreated the lock between acquire and release — the library restores the quarantine if possible and refuses to delete a lock it may not own, preventing clobbering another process's lock.","triggerScenarios":"Calling the release function created by createSettingsLock when the lock file at lockPath was replaced after acquisition (different inode/device), detected by sameFileIdentity(quarantinedStats, ownedStats) returning false.","commonSituations":"Another process force-deleted and recreated the stale lock; a cleanup cron removed the lock mid-run; the lock lives on a filesystem where rename/lstat identity behaves unexpectedly; long-running process outlived a lock sweeper.","solutions":["Inspect lockPath manually: if no ECC process is active, remove the lock file and retry.","Check whether another process or cron job is deleting/recreating lock files and stop it.","Keep ECC processes short-lived relative to lock sweeper intervals to avoid lock replacement mid-run."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await withSettingsLock(settingsPath, work);\n} catch (e) {\n  if (e.message.startsWith('Refusing to release a changed Claude settings lock')) {\n    // lock was replaced; verify no ECC process running, then remove lockPath manually\n  } else throw e;\n}","preventionTips":["Don't run cron/cleanup jobs that delete ECC lock files while ECC runs.","Keep ECC operations short so external lock sweepers don't replace locks mid-run.","Only remove stale locks manually after confirming no ECC process is active."],"tags":["locking","concurrency","filesystem","safety-gate"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}