ruvnet/ruflo · error

Competing Meta-Proxy pid ${current.pid} won the port with ve

Error message

Competing Meta-Proxy pid ${current.pid} won the port with version ${current.version}.

What it means

launchAndVerify() polls the effective proxy after launching the new binary, expecting the probe to return the pid it just started with the expected version and executable. If the probe instead returns a different pid, another daemon won the port race; the library kills its own (portless) child and throws, reporting the competing pid and version.

Source

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

  const log = fs.openSync(proxyLogFilePath(), 'a', 0o600);
  try {
    const child = spawn(binary, [], { detached: true, stdio: ['ignore', log, log], windowsHide: true });
    if (!child.pid) throw new Error('Meta-Proxy did not return a process id.');
    child.unref();
    fs.writeFileSync(proxyPidFilePath(), `${child.pid}\n`, { mode: 0o600 });
    return child.pid;
  } finally { fs.closeSync(log); }
}

async function launchAndVerify(binary: string, version: string, wait: Wait): Promise<EffectiveProxy> {
  const pid = launch(binary);
  for (let attempt = 0; attempt < 100; attempt++) {
    await wait(50);
    const current = await probeEffectiveProxy();
    if (current?.pid === pid && current.version === version && path.resolve(current.executable) === path.resolve(binary)) return current;
    if (current && current.pid !== pid) {
      try { process.kill(pid, 'SIGTERM'); } catch { /* already exited */ }
      throw new Error(`Competing Meta-Proxy pid ${current.pid} won the port with version ${current.version}.`);
    }
  }
  try { process.kill(pid, 'SIGTERM'); } catch { /* already exited */ }
  throw new Error(`Meta-Proxy v${version} did not become the effective daemon.`);
}

export async function installAndActivateProxy(version: string, log?: (line: string) => void): Promise<InstallResult & { pid: number }> {
  const wait = waitNormally;
  const binary = proxyBinaryPath();
  const manifest = proxyInstallManifestPath();
  const binaryBackup = `${binary}.rollback`;
  const manifestBackup = `${manifest}.rollback`;
  let release: (() => void) | null = null;
  let prior: EffectiveProxy | null = null;
  try {
    release = await acquireProxyInstallLease(wait);
    prior = await stopEffective(wait);
    fs.rmSync(binaryBackup, { force: true });

View on GitHub (pinned to 29f048fc3b)

Solutions

  1. Ensure only one activation runs at a time — the install lease helps, but don't start daemons manually in parallel
  2. Stop any service manager unit that respawns meta-proxy, run the install, then re-enable it
  3. Find and stop the competing daemon (pid shown in the message), then retry installAndActivateProxy
  4. Re-run the activation after a few seconds; if it recurs, check the proxy log and port usage (lsof -i :11435) to find who keeps binding

Example fix

// before: respawn racing the upgrade
[program:meta-proxy]
autorestart=true
// after
# supervisorctl stop meta-proxy
# npx ruflo proxy install --version 1.2.3
# supervisorctl start meta-proxy
Defensive patterns

Strategy: retry

Validate before calling

// Before activating, make sure no other activation/daemon is mid-start
const existing = await probeEffectiveProxy();
if (existing) console.log(`Existing proxy: pid ${existing.pid} v${existing.version} (${existing.executable}) — stop it first`);

Type guard

null

Try / catch

try {
  await installAndActivateProxy(version);
} catch (e) {
  if (e instanceof Error && e.message.includes('won the port')) {
    await new Promise(r => setTimeout(r, 10_000));
    await installAndActivateProxy(version); // competing transient daemon usually exits
  } else throw e;
}

Prevention

When it happens

Trigger: installAndActivateProxy() or effective() → launchAndVerify(): while waiting up to 5s for the freshly spawned daemon to bind, another meta-proxy process (started concurrently, or respawned by a supervisor from the still-installed old binary) grabbed the port first.

Common situations: Two terminals/CI jobs activating the proxy at once; systemd restarting the previous daemon just after it was stopped; an old detached meta-proxy that hadn't fully exited when the new one launched; auto-update tooling launching its own instance.

Related errors


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