{"record":{"id":"a6bd4a7494f054f9","repo":"ruvnet/ruflo","slug":"another-ruflo-metaharness-installer-still-owns-the","errorCode":null,"errorMessage":"Another Ruflo/MetaHarness installer still owns the Meta-Proxy install lease.","messagePattern":"Another Ruflo/MetaHarness installer still owns the Meta-Proxy install lease\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/proxy/activation.ts","lineNumber":106,"sourceCode":"  for (let attempt = 0; attempt < 200; attempt++) {\n    try {\n      fs.mkdirSync(lock, { mode: 0o700 });\n      fs.writeFileSync(path.join(lock, 'owner'), `${process.pid}\\n`, { mode: 0o600 });\n      return () => fs.rmSync(lock, { recursive: true, force: true });\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n      try {\n        const age = Date.now() - fs.statSync(lock).mtimeMs;\n        const owner = Number.parseInt(fs.readFileSync(path.join(lock, 'owner'), 'utf8').trim(), 10);\n        if (age > 120_000 && (!Number.isSafeInteger(owner) || owner <= 0 || !processExists(owner))) {\n          fs.rmSync(lock, { recursive: true, force: true });\n          continue;\n        }\n      } catch { /* another installer may still be writing its owner */ }\n      await wait(50);\n    }\n  }\n  throw new Error('Another Ruflo/MetaHarness installer still owns the Meta-Proxy install lease.');\n}\n\nasync function stopEffective(wait: Wait): Promise<EffectiveProxy | null> {\n  const owner = await probeEffectiveProxy();\n  if (!owner) return null;\n  if (!isSupportedOwner(owner.executable, process.platform)) {\n    throw new Error(`Meta-Proxy port owner pid ${owner.pid} is not a recognized Ruflo/MetaHarness binary; refusing to signal it.`);\n  }\n  process.kill(owner.pid, 'SIGTERM');\n  for (let attempt = 0; attempt < 40; attempt++) {\n    await wait(50);\n    const current = await probeEffectiveProxy();\n    if (!current || current.pid !== owner.pid) return owner;\n  }\n  throw new Error(`Stale Meta-Proxy pid ${owner.pid} did not stop.`);\n}\n\nfunction launch(binary: string): number {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/ruvnet/ruflo/blob/29f048fc3b556f857cf2b126d2a84c19d2daa0d0/v3/@claude-flow/cli/src/proxy/activation.ts#L88-L124","documentation":"acquireProxyInstallLease() uses a lock directory (with an owner pid file) to ensure only one installer modifies the Meta-Proxy install at a time. It retries up to 200 times (50ms apart, ~10s, plus reclaiming leases older than 120s whose owner pid is dead). If the lock is still held after all attempts, it concludes another concurrent installer owns the lease and throws.","triggerScenarios":"Calling installAndActivateProxy() (which calls acquireProxyInstallLease via its flow) while another Ruflo/MetaHarness install/upgrade is in progress; a crashed installer left a lock younger than 120s; a lock owner pid file exists and its recorded pid is still alive; a foreign process created the lock directory.","commonSituations":"Running two `ruflo`/`claude-flow` CLI installs concurrently (e.g. in parallel CI jobs or two terminal tabs); a previous install was killed mid-run leaving a fresh lock; another user on the same home directory is installing; CI matrix jobs sharing a cached HOME.","solutions":["Wait for the other installer to finish and retry; the lock self-heals only after it is older than 120s with a dead owner pid","Find the owner: cat the `owner` file inside the install-lock directory and check `ps -p <pid>`; if that process is dead and the lock is fresh, wait 120s for stale-lock reclamation","If you are certain no installer is running, delete the lock directory manually and retry","Serialize installs in CI: don't run multiple jobs that call installAndActivateProxy against the same HOME simultaneously"],"exampleFix":"// before (parallel CI)\n- run: npx ruflo install &\n- run: npx ruflo install &\n// after (serialized)\n- run: npx ruflo install\n- run: npx ruflo install","handlingStrategy":"retry","validationCode":"// Check for an active install lease before attempting\nconst lock = proxyInstallLockPath();\ntry {\n  const owner = Number(fs.readFileSync(path.join(lock, 'owner'), 'utf8').trim());\n  if (process.kill(owner, 0)) throw new Error(`Installer pid ${owner} is still running; wait for it`);\n} catch (e) {\n  if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;\n}\nawait installAndActivateProxy(version);","typeGuard":"null","tryCatchPattern":"try {\n  await installAndActivateProxy(version);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('still owns the Meta-Proxy install lease')) {\n    await new Promise(r => setTimeout(r, 30_000)); // let the other installer finish\n    await installAndActivateProxy(version);\n  } else throw e;\n}","preventionTips":["Never run two installs/upgrades against the same HOME concurrently; serialize them","In CI, use a lock/mutex (or per-job HOME) for proxy installation steps","After a killed install, wait >120s or manually remove the stale lock directory","Alert on the lock directory's age in long-lived dev containers"],"tags":["concurrency","lock","install","timeout"],"backgroundTag":"install-lock-contention","analyzedSha":"29f048fc3b556f857cf2b126d2a84c19d2daa0d0","analyzedAt":"2026-09-01T11:37:30.251Z","contentChangedAt":"2026-09-01T11:37:30.251Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}