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
- Run the trial proxy in the foreground with the same env to see its actual startup error
- Check for an orphaned listener: `lsof -i :<port>` or `ss -ltnp`, and kill stale processes
- Raise the readiness timeout passed to the launcher
- 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
- Clean up spawned proxies in finally blocks and signal handlers
- Pre-check that the port is free before launching
- Log child stderr — ready-timeouts are usually a crashed child in disguise
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
- caveman agent: Cave Runtime failed to start (${error instanc
- caveman agent: Cave Runtime did not become ready at ${gatewa
- binary download failed: ${error.message}
- AbortError
- cave_transform_registry_unavailable: run caveman setup or se
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-18).
Data as JSON: /api/errors/33a8b78a650262ce.
Report an issue: GitHub.