{"record":{"id":"5250ccaac965f073","repo":"JuliusBrussee/caveman","slug":"caveman-agent-cave-runtime-did-not-become-ready-a","errorCode":null,"errorMessage":"caveman agent: Cave Runtime did not become ready at ${gatewayURL}; run caveman setup --install","messagePattern":"caveman agent: Cave Runtime did not become ready at (.+?); run caveman setup --install","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":5269,"sourceCode":"        \"caveman agent: Caveman CLI not found; run npm install, then caveman setup --install\",\n      )\n      : new Error(`caveman agent: Cave Runtime failed to start (${error.message})`);\n  });\n  child.once(\"exit\", (code, signal) => {\n    if (startupFailure !== undefined || code === 0) return;\n    startupFailure = new Error(\n      `caveman agent: Cave Runtime failed to start (caveman start exited ${signal ?? code}); run caveman setup --install`,\n    );\n  });\n  child.unref();\n  const deadline = Date.now() + 10_000;\n  while (Date.now() < deadline) {\n    await new Promise((resolve) => setTimeout(resolve, 100));\n    if (startupFailure !== undefined) throw startupFailure;\n    const startedBilling = await runtimeReady(gatewayURL, fetchImpl);\n    if (startedBilling !== undefined) return startedBilling;\n  }\n  throw new Error(\n    `caveman agent: Cave Runtime did not become ready at ${gatewayURL}; run caveman setup --install`,\n  );\n}\n\nasync function runtimeReady(\n  gatewayURL: string,\n  fetchImpl: typeof globalThis.fetch,\n): Promise<GatewayProviderBilling | undefined> {\n  const identity = await gatewayIdentity(gatewayURL, fetchImpl);\n  if (identity === undefined) return undefined;\n  return await localProxyOwned(new URL(gatewayURL)) ? identity.providerBilling : undefined;\n}\n\n// A gateway hostname is loopback only if the traffic can never leave the host.\n// WHATWG URL returns IPv6 literals bracketed (\"[::1]\"), so both forms are\n// checked; 127.0.0.0/8 and 0.0.0.0 all route to localhost and must not be\n// treated as remote (nor as needing https).\nfunction isLoopbackHostname(hostname: string): boolean {","sourceCodeStart":5251,"sourceCodeEnd":5287,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/runtime.ts#L5251-L5287","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run caveman status / caveman doctor and look at why the started process died (logs, port already in use)","Free the gateway port (kill the stale caveman process) and retry","Pre-warm the gateway outside the run: caveman start, then poll /health/ready yourself with a longer deadline, then invoke the run","Reinstall once with caveman setup --install to rule out a broken runtime install"],"exampleFix":"# before: cold box, first run pays the 10s startup deadline and fails\n# after: pre-warm in a CI step, then run\ncaveman setup --install\ncaveman start\nuntil curl -fsS http://127.0.0.1:$PORT/health/ready >/dev/null; do sleep 0.2; done","handlingStrategy":"retry","validationCode":"async function startGatewayAndWait(gatewayURL, timeoutMs = 30_000) {\n  spawn('caveman', ['start'], { detached: true, stdio: 'ignore' }).unref();\n  const deadline = Date.now() + timeoutMs;\n  while (Date.now() < deadline) {\n    const res = await fetch(gatewayURL + '/health/ready').catch(() => null);\n    if (res && res.ok) return;\n    await new Promise((r) => setTimeout(r, 100));\n  }\n  throw new Error('gateway not ready within ' + timeoutMs + 'ms');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await run(agent, options);\n} catch (error) {\n  if (error instanceof Error && error.message.includes('did not become ready')) {\n    await startGatewayAndWait(gatewayURL); // pre-warm with your own, longer deadline\n    return run(agent, options);\n  }\n  throw error;\n}","preventionTips":["Warm the gateway in a pre-run CI step so the framework's 10s startup deadline is never the first boot","Ensure the gateway port is free before runs (stale caveman processes are the usual culprit)","On slow machines/containers, manage the runtime yourself with ensureRuntime:false after your own readiness poll"],"tags":["gateway","startup","timeout","cli","loopback"],"backgroundTag":"service-startup-timeout","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}