JuliusBrussee/caveman · error

caveman agent: Cave Runtime did not become ready at ${gatewa

Error message

caveman agent: Cave Runtime did not become ready at ${gatewayURL}; run caveman setup --install

What it means

Thrown after the runtime spawned a detached `caveman start` and polled the gateway every 100ms for a 10-second deadline without it ever becoming Caveman-ready (runtimeReady kept returning undefined and no early startupFailure fired). The gateway process either crashed after spawn, never bound the expected port, or was too slow to boot; the message directs you to caveman setup --install.

Source

Thrown at packages/agent/src/runtime.ts:5269

        "caveman agent: Caveman CLI not found; run npm install, then caveman setup --install",
      )
      : new Error(`caveman agent: Cave Runtime failed to start (${error.message})`);
  });
  child.once("exit", (code, signal) => {
    if (startupFailure !== undefined || code === 0) return;
    startupFailure = new Error(
      `caveman agent: Cave Runtime failed to start (caveman start exited ${signal ?? code}); run caveman setup --install`,
    );
  });
  child.unref();
  const deadline = Date.now() + 10_000;
  while (Date.now() < deadline) {
    await new Promise((resolve) => setTimeout(resolve, 100));
    if (startupFailure !== undefined) throw startupFailure;
    const startedBilling = await runtimeReady(gatewayURL, fetchImpl);
    if (startedBilling !== undefined) return startedBilling;
  }
  throw new Error(
    `caveman agent: Cave Runtime did not become ready at ${gatewayURL}; run caveman setup --install`,
  );
}

async function runtimeReady(
  gatewayURL: string,
  fetchImpl: typeof globalThis.fetch,
): Promise<GatewayProviderBilling | undefined> {
  const identity = await gatewayIdentity(gatewayURL, fetchImpl);
  if (identity === undefined) return undefined;
  return await localProxyOwned(new URL(gatewayURL)) ? identity.providerBilling : undefined;
}

// A gateway hostname is loopback only if the traffic can never leave the host.
// WHATWG URL returns IPv6 literals bracketed ("[::1]"), so both forms are
// checked; 127.0.0.0/8 and 0.0.0.0 all route to localhost and must not be
// treated as remote (nor as needing https).
function isLoopbackHostname(hostname: string): boolean {

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Run caveman status / caveman doctor and look at why the started process died (logs, port already in use)
  2. Free the gateway port (kill the stale caveman process) and retry
  3. Pre-warm the gateway outside the run: caveman start, then poll /health/ready yourself with a longer deadline, then invoke the run
  4. Reinstall once with caveman setup --install to rule out a broken runtime install

Example fix

# before: cold box, first run pays the 10s startup deadline and fails
# after: pre-warm in a CI step, then run
caveman setup --install
caveman start
until curl -fsS http://127.0.0.1:$PORT/health/ready >/dev/null; do sleep 0.2; done
Defensive patterns

Strategy: retry

Validate before calling

async function startGatewayAndWait(gatewayURL, timeoutMs = 30_000) {
  spawn('caveman', ['start'], { detached: true, stdio: 'ignore' }).unref();
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const res = await fetch(gatewayURL + '/health/ready').catch(() => null);
    if (res && res.ok) return;
    await new Promise((r) => setTimeout(r, 100));
  }
  throw new Error('gateway not ready within ' + timeoutMs + 'ms');
}

Try / catch

try {
  await run(agent, options);
} catch (error) {
  if (error instanceof Error && error.message.includes('did not become ready')) {
    await startGatewayAndWait(gatewayURL); // pre-warm with your own, longer deadline
    return run(agent, options);
  }
  throw error;
}

Prevention

When it happens

Trigger: Loopback ensure path: spawn succeeded, but /health/ready at gatewayURL did not report a Caveman identity owned by this proxy within Date.now() + 10_000 ms — cold machines, port conflicts, gateway crashing during boot, or the URL pointing at a different port than the gateway bound.

Common situations: Cold CI container with slow first boot; a stale caveman process holding the port so the new one exits immediately; gateway URL/port misconfiguration; heavily loaded developer machine where 10s is not enough for first boot.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/5250ccaac965f073. Report an issue: GitHub.