affaan-m/ECC · warning

Another Nasiko lifecycle operation won lock acquisition

Error message

Another Nasiko lifecycle operation won lock acquisition: ${lockPath}.

What it means

During lock acquisition, when creating the lock file fails with EEXIST, the library inspects the existing lock. If the lock vanishes (ENOENT) between inspection and a re-create attempt that again hits EEXIST, another process has won the race in the interim; the library throws this error rather than retrying indefinitely. It signals benign but unresolved lock contention.

Solutions

  1. Re-run the lifecycle operation after the other process finishes (the error is transient contention).
  2. Serialize lifecycle operations in your tooling (a job queue or flock around the CLI call).
  3. Check the lock path listed in the message for a live owner (ps -p <pid> from the lock's pid field) and wait.
  4. Retry with backoff; this is a lost race, not a corrupt state.

Example fix

// before
installNasiko({ yes: true }) // throws in parallel CI
// after
try {
  installNasiko({ yes: true })
} catch (e) {
  if (String(e.message).includes('won lock acquisition')) await sleep(1000), installNasiko({ yes: true })
  else throw e
}
Defensive patterns

Strategy: retry

Validate before calling

// Before install, check for a live lock owner:
const lockPath = path.join(installDir, '.ecc-nasiko-lifecycle.lock')
if (fs.existsSync(lockPath)) {
  const { pid } = JSON.parse(fs.readFileSync(lockPath, 'utf8'))
  try { process.kill(pid, 0); throw new Error(`operation ${pid} still running`) } catch (e) { if (e.code !== 'ESRCH') throw e }
}

Try / catch

async function installWithRetry(opts, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await installNasiko(opts) }
    catch (e) {
      if (!/won lock acquisition|changed during/.test(e.message) || i === attempts - 1) throw e
      await new Promise(r => setTimeout(r, 500 * 2 ** i))
    }
  }
}

Prevention

When it happens

Trigger: Two or more installNasiko/uninstallNasiko processes racing on the same install directory: process A's lock is removed exactly while process B re-attempts createLifecycleLock, and B's exclusive create loses to a third contender.

Common situations: Parallel CI jobs installing nasiko into a shared directory; concurrent install and uninstall invocations in different terminals; a cron job and a manual install colliding.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/26dfab53b890d5a5. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/nasiko-release.js:355

  };
}

function acquireLifecycleLock(installDirectory, fileSystem = fs, options = {}) {
  const lockPath = path.join(installDirectory, '.ecc-nasiko-lifecycle.lock');
  try {
    return createLifecycleLock(lockPath, fileSystem);
  } catch (error) {
    if (error.code !== 'EEXIST') throw error;
  }

  let existing;
  try { existing = inspectLifecycleLock(lockPath, fileSystem); }
  catch (error) {
    if (error.code === 'ENOENT') {
      try { return createLifecycleLock(lockPath, fileSystem); }
      catch (retryError) {
        if (retryError.code === 'EEXIST') {
          throw new Error(`Another Nasiko lifecycle operation won lock acquisition: ${lockPath}.`);
        }
        throw retryError;
      }
    }
    throw error;
  }
  const isProcessAlive = options.isProcessAlive || processIsAlive;
  if (!existing || isProcessAlive(existing.metadata.pid)) {
    throw new Error(`Another Nasiko lifecycle operation is already in progress; inspect ${lockPath} before recovering a stale lock.`);
  }
  if (!removeLockIfOwned(lockPath, existing.stats, fileSystem)) {
    throw new Error(`Nasiko lifecycle lock changed during stale-owner recovery: ${lockPath}.`);
  }
  try {
    return createLifecycleLock(lockPath, fileSystem);
  } catch (error) {
    if (error.code === 'EEXIST') {
      throw new Error(`Another Nasiko lifecycle operation won stale-lock recovery: ${lockPath}.`);

View on GitHub (pinned to 8321021c54)