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
- 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
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
- 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
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
- timed out acquiring workspace-lease lock
- timed out acquiring ai-budget lock
- timed out acquiring flywheel attempts lock
- timed out acquiring repo-supervisor lock
- timed out acquiring flywheel transaction lock
AI-assisted analysis of ruvnet/ruflo@29f048fc3b (2026-09-01).
Data as JSON: /api/errors/a6bd4a7494f054f9.
Report an issue: GitHub.