ruvnet/ruflo · error

Stale Meta-Proxy pid ${owner.pid} did not stop.

Error message

Stale Meta-Proxy pid ${owner.pid} did not stop.

What it means

After sending SIGTERM to the recognized daemon on the proxy port, stopEffective() polls up to 40 times at 50ms (2s total) waiting for it to exit. If the process still holds the port after that window, the library gives up and throws rather than proceeding with an upgrade against a still-running old daemon.

Source

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

      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 {
  fs.mkdirSync(path.dirname(proxyLogFilePath()), { recursive: true, mode: 0o700 });
  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);

View on GitHub (pinned to 29f048fc3b)

Solutions

  1. Wait a few seconds and retry installAndActivateProxy — transient slow shutdown often resolves
  2. Check the daemon log (proxyLogFilePath()) for shutdown errors and kill the process manually if it is wedged: kill <pid>, then kill -9 <pid> if needed
  3. If a service manager respawns the daemon, stop/disable that unit (systemctl stop/disable or supervisorctl stop) before running the CLI install
  4. Reboot or clean up zombie/stuck processes if the pid is in uninterruptible sleep (D state)

Example fix

// before: supervisor auto-restarts meta-proxy during upgrade
[program:meta-proxy]
autorestart=true
// after: stop it before upgrading
# supervisorctl stop meta-proxy && npx ruflo proxy install ...
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

try {
  await installAndActivateProxy(version);
} catch (e) {
  if (e instanceof Error && e.message.includes('did not stop')) {
    await new Promise(r => setTimeout(r, 5_000));
    await installAndActivateProxy(version); // one retry after slow shutdown settles
  } else throw e;
}

Prevention

When it happens

Trigger: installAndActivateProxy() → stopEffective(): the old meta-proxy received SIGTERM but did not exit within ~2 seconds — typically because it is hung, stuck in uninterruptible I/O, ignoring SIGTERM, blocked shutting down active connections, or running under a supervisor that restarts it faster than the poll window.

Common situations: The daemon is wedged after a system suspend or heavy load; a systemd/launchd/supervisord unit immediately respawns meta-proxy; the process is in D-state on a stuck mount; very slow disks delay graceful shutdown.

Related errors


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