{"record":{"id":"92b52a8185752730","repo":"affaan-m/ECC","slug":"nasiko-lifecycle-lock-changed-during-stale-owner-recovery","errorCode":null,"errorMessage":"Nasiko lifecycle lock changed during stale-owner recovery: ${lockPath}.","messagePattern":"Nasiko lifecycle lock changed during stale-owner recovery: (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"scripts/lib/nasiko-release.js","lineNumber":367,"sourceCode":"  try { existing = inspectLifecycleLock(lockPath, fileSystem); }\n  catch (error) {\n    if (error.code === 'ENOENT') {\n      try { return createLifecycleLock(lockPath, fileSystem); }\n      catch (retryError) {\n        if (retryError.code === 'EEXIST') {\n          throw new Error(`Another Nasiko lifecycle operation won lock acquisition: ${lockPath}.`);\n        }\n        throw retryError;\n      }\n    }\n    throw error;\n  }\n  const isProcessAlive = options.isProcessAlive || processIsAlive;\n  if (!existing || isProcessAlive(existing.metadata.pid)) {\n    throw new Error(`Another Nasiko lifecycle operation is already in progress; inspect ${lockPath} before recovering a stale lock.`);\n  }\n  if (!removeLockIfOwned(lockPath, existing.stats, fileSystem)) {\n    throw new Error(`Nasiko lifecycle lock changed during stale-owner recovery: ${lockPath}.`);\n  }\n  try {\n    return createLifecycleLock(lockPath, fileSystem);\n  } catch (error) {\n    if (error.code === 'EEXIST') {\n      throw new Error(`Another Nasiko lifecycle operation won stale-lock recovery: ${lockPath}.`);\n    }\n    throw error;\n  }\n}\n\nasync function installNasiko(options = {}, dependencies = {}) {\n  const version = options.version || 'v0.1.0';\n  const base = getQualifiedRelease(version, dependencies.platform || process.platform, dependencies.arch || process.arch);\n  const release = dependencies.releaseOverride ? { ...base, ...dependencies.releaseOverride } : base;\n  const installDirectory = validateInstallDirectory(options.installDir || defaultInstallDirectory(release, dependencies.environment || process.env, dependencies.homeDirectory || os.homedir()));\n  const destination = path.join(installDirectory, release.binaryName);\n  const plan = { dryRun: Boolean(options.dryRun), version, platform: release.os, architecture: release.arch, manifestDigest: release.manifestDigest, binaryDigest: release.binaryDigest, registryOrigin: REGISTRY_ORIGIN, destination, license: release.license, sourceUrl: release.sourceUrl };","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/nasiko-release.js#L349-L385","documentation":"removeLockIfOwned removes the stale lock only if its current file identity (dev/ino) still matches the stats captured during inspection. If the lock file was replaced or removed by someone else in the meantime, removal is refused and this error is thrown — the lock state changed under us during recovery and it is unsafe to proceed.","triggerScenarios":"installNasiko/uninstallNasiko recovering a stale lock while a third process concurrently deletes and recreates the lock file between inspectLifecycleLock and removeLockIfOwned (inode mismatch).","commonSituations":"High-contention shared install directories in CI matrices; recovery racing with another recovery attempt; external cleanup scripts (tmpwatch/cron) removing lock files mid-operation.","solutions":["Simply retry the operation — the race is transient and the next attempt will see consistent state.","Stop external cleanup jobs from touching the install directory's lock file.","Serialize lifecycle operations across the machines/jobs sharing the directory.","Verify no other recovery attempt is running (check for multiple concurrent invocations) before retrying."],"exampleFix":"// before\ninstallNasiko({ yes: true }) // throws once during a race\n// after\nlet last\nfor (let i = 0; i < 3; i++) {\n  try { last = installNasiko({ yes: true }); break }\n  catch (e) { if (!/changed during/.test(e.message)) throw e; await sleep(500) }\n}","handlingStrategy":"retry","validationCode":"// Ensure exclusive access before recovery:\nif (fs.existsSync(path.join(installDir, '.ecc-nasiko-lifecycle.lock'))) {\n  // only one recovery attempt at a time — coordinate via an external mutex/flock\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installNasiko({ yes: true })\n} catch (e) {\n  if (e.message.includes('changed during stale-owner recovery')) {\n    await new Promise(r => setTimeout(r, 1000))\n    return installNasiko({ yes: true }) // next attempt sees consistent state\n  }\n  throw e\n}","preventionTips":["Exclude lock files from external cleanup jobs (tmpwatch/cron).","Attempt stale-lock recovery from one coordinator, not multiple jobs at once.","Retry once on this error before surfacing it to users."],"tags":["concurrency","locking","race-condition"],"backgroundTag":"address-already-in-use","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"}