ruvnet/ruflo · error

Another Ruflo/MetaHarness installer still owns the Meta-Prox

Error message

Another Ruflo/MetaHarness installer still owns the Meta-Proxy install lease.

What it means

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.

Source

Thrown at v3/@claude-flow/cli/src/proxy/activation.ts:106

  for (let attempt = 0; attempt < 200; attempt++) {
    try {
      fs.mkdirSync(lock, { mode: 0o700 });
      fs.writeFileSync(path.join(lock, 'owner'), `${process.pid}\n`, { mode: 0o600 });
      return () => fs.rmSync(lock, { recursive: true, force: true });
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
      try {
        const age = Date.now() - fs.statSync(lock).mtimeMs;
        const owner = Number.parseInt(fs.readFileSync(path.join(lock, 'owner'), 'utf8').trim(), 10);
        if (age > 120_000 && (!Number.isSafeInteger(owner) || owner <= 0 || !processExists(owner))) {
          fs.rmSync(lock, { recursive: true, force: true });
          continue;
        }
      } catch { /* another installer may still be writing its owner */ }
      await wait(50);
    }
  }
  throw new Error('Another Ruflo/MetaHarness installer still owns the Meta-Proxy install lease.');
}

async function stopEffective(wait: Wait): Promise<EffectiveProxy | null> {
  const owner = await probeEffectiveProxy();
  if (!owner) return null;
  if (!isSupportedOwner(owner.executable, process.platform)) {
    throw new Error(`Meta-Proxy port owner pid ${owner.pid} is not a recognized Ruflo/MetaHarness binary; refusing to signal it.`);
  }
  process.kill(owner.pid, 'SIGTERM');
  for (let attempt = 0; attempt < 40; attempt++) {
    await wait(50);
    const current = await probeEffectiveProxy();
    if (!current || current.pid !== owner.pid) return owner;
  }
  throw new Error(`Stale Meta-Proxy pid ${owner.pid} did not stop.`);
}

function launch(binary: string): number {

View on GitHub (pinned to 29f048fc3b)

Solutions

  1. 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
  2. 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
  3. If you are certain no installer is running, delete the lock directory manually and retry
  4. Serialize installs in CI: don't run multiple jobs that call installAndActivateProxy against the same HOME simultaneously

Example fix

// before (parallel CI)
- run: npx ruflo install &
- run: npx ruflo install &
// after (serialized)
- run: npx ruflo install
- run: npx ruflo install
Defensive patterns

Strategy: retry

Validate before calling

// Check for an active install lease before attempting
const lock = proxyInstallLockPath();
try {
  const owner = Number(fs.readFileSync(path.join(lock, 'owner'), 'utf8').trim());
  if (process.kill(owner, 0)) throw new Error(`Installer pid ${owner} is still running; wait for it`);
} catch (e) {
  if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;
}
await installAndActivateProxy(version);

Type guard

null

Try / catch

try {
  await installAndActivateProxy(version);
} catch (e) {
  if (e instanceof Error && e.message.includes('still owns the Meta-Proxy install lease')) {
    await new Promise(r => setTimeout(r, 30_000)); // let the other installer finish
    await installAndActivateProxy(version);
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruvnet/ruflo@29f048fc3b (2026-09-01). Data as JSON: /api/errors/a6bd4a7494f054f9. Report an issue: GitHub.