{"record":{"id":"33a8b78a650262ce","repo":"JuliusBrussee/caveman","slug":"caveman-trial-proxy-did-not-become-ready-on-host","errorCode":null,"errorMessage":"caveman trial proxy did not become ready on ${host}:${port}","messagePattern":"caveman trial proxy did not become ready on (.+?):(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":16493,"sourceCode":"\nfunction freePort(): Promise<number> {\n  return new Promise((resolve, reject) => {\n    const server = netCreateServer();\n    server.on(\"error\", reject);\n    server.listen(0, \"127.0.0.1\", () => {\n      const addr = server.address() as AddressInfo;\n      server.close(() => resolve(addr.port));\n    });\n  });\n}\n\nasync function waitForPort(host: string, port: number, timeoutMs: number): Promise<void> {\n  const deadline = Date.now() + timeoutMs;\n  while (Date.now() < deadline) {\n    if (await portListening(host, port)) return;\n    await sleep(100);\n  }\n  throw new Error(`caveman trial proxy did not become ready on ${host}:${port}`);\n}\n\nfunction waitForChild(child: ReturnType<typeof spawn>, timeoutMs: number): Promise<void> {\n  return new Promise((resolve) => {\n    let done = false;\n    const finish = () => {\n      if (done) return;\n      done = true;\n      resolve();\n    };\n    child.once(\"exit\", finish);\n    child.once(\"close\", finish);\n    setTimeout(() => {\n      try { child.kill(\"SIGKILL\"); } catch {}\n      finish();\n    }, timeoutMs).unref();\n  });\n}","sourceCodeStart":16475,"sourceCodeEnd":16511,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L16475-L16511","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight: the port must be free before spawning the trial proxy.\nimport { createConnection } from 'node:net';\nconst inUse = await new Promise<boolean>((resolve) => {\n  const sock = createConnection(port, host);\n  sock.on('connect', () => { sock.destroy(); resolve(true); });\n  sock.on('error', () => resolve(false));\n});\nif (inUse) throw new Error(`port ${host}:${port} occupied — kill the stale listener first`);","typeGuard":null,"tryCatchPattern":"try {\n  await waitForPort(host, port, 10_000);\n} catch (e) {\n  if ((e as Error).message.includes('did not become ready')) {\n    await killStaleChildren();      // most timeouts are a crashed/stale child\n    await waitForPort(host, port, 30_000);\n  } else throw e;\n}","preventionTips":["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"],"tags":["startup","timeout","port","proxy","spawn"],"backgroundTag":"service-startup-timeout","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}