JuliusBrussee/caveman · error · Error

caveman trial proxy did not become ready on ${host}:${port}

Error message

caveman trial proxy did not become ready on ${host}:${port}

What it means

The trial-proxy launcher spawns the proxy as a child process and polls host:port every 100 ms until a deadline. If the port never becomes listening within timeoutMs, waitForPort throws this ready-timeout naming the expected address. It means the child crashed on startup, bound a different address, or was too slow to accept connections.

Source

Thrown at packages/cli/src/index.ts:16493

function freePort(): Promise<number> {
  return new Promise((resolve, reject) => {
    const server = netCreateServer();
    server.on("error", reject);
    server.listen(0, "127.0.0.1", () => {
      const addr = server.address() as AddressInfo;
      server.close(() => resolve(addr.port));
    });
  });
}

async function waitForPort(host: string, port: number, timeoutMs: number): Promise<void> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (await portListening(host, port)) return;
    await sleep(100);
  }
  throw new Error(`caveman trial proxy did not become ready on ${host}:${port}`);
}

function waitForChild(child: ReturnType<typeof spawn>, timeoutMs: number): Promise<void> {
  return new Promise((resolve) => {
    let done = false;
    const finish = () => {
      if (done) return;
      done = true;
      resolve();
    };
    child.once("exit", finish);
    child.once("close", finish);
    setTimeout(() => {
      try { child.kill("SIGKILL"); } catch {}
      finish();
    }, timeoutMs).unref();
  });
}

View on GitHub (pinned to 5184b3d11a)

Solutions

  1. Run the trial proxy in the foreground with the same env to see its actual startup error
  2. Check for an orphaned listener: `lsof -i :<port>` or `ss -ltnp`, and kill stale processes
  3. Raise the readiness timeout passed to the launcher
  4. Confirm the polled host matches the address the proxy actually binds
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: the port must be free before spawning the trial proxy.
import { createConnection } from 'node:net';
const inUse = await new Promise<boolean>((resolve) => {
  const sock = createConnection(port, host);
  sock.on('connect', () => { sock.destroy(); resolve(true); });
  sock.on('error', () => resolve(false));
});
if (inUse) throw new Error(`port ${host}:${port} occupied — kill the stale listener first`);

Try / catch

try {
  await waitForPort(host, port, 10_000);
} catch (e) {
  if ((e as Error).message.includes('did not become ready')) {
    await killStaleChildren();      // most timeouts are a crashed/stale child
    await waitForPort(host, port, 30_000);
  } else throw e;
}

Prevention

When it happens

Trigger: Child process exits immediately (bad env, missing config, port already taken by another listener); proxy binds a different interface than the polled host; machine under heavy load so startup exceeds the deadline; sandboxed CI where binding sockets is restricted.

Common situations: CI environments with locked-down or ephemeral ports; a stale proxy from a previous run still holding the port; polling localhost while the proxy binds a container IP; slow cold start on first invocation.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18). Data as JSON: /api/errors/33a8b78a650262ce. Report an issue: GitHub.