{"record":{"id":"2a79f37bc8e21b9c","repo":"can1357/oh-my-pi","slug":"argv-0-did-not-report-a-tunnel-url-within-re","errorCode":null,"errorMessage":"${argv[0]} did not report a tunnel URL within ${READY_TIMEOUT_MS / 1000}s","messagePattern":"(.+?) did not report a tunnel URL within (.+?)s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/exposure.ts","lineNumber":284,"sourceCode":"\t\t\t\t\t\tbaseUrl = normalizeBaseUrl(url);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tscanned = text.lastIndexOf(\"\\n\") + 1;\n\t\t\t}\n\t\t\t// The URL banner can precede edge registration (cloudflared prints the\n\t\t\t// hostname before any connection is live); wait for the ready marker.\n\t\t\tif (baseUrl !== undefined && (!readyPattern || readyPattern.test(text))) {\n\t\t\t\treturn { proc, baseUrl };\n\t\t\t}\n\t\t}\n\t\tif (exitCode !== null) {\n\t\t\tthrow new Error(`${argv[0]} exited with code ${exitCode} before reporting a tunnel URL`);\n\t\t}\n\t\tawait Bun.sleep(150);\n\t}\n\tkillTunnelProcess(proc);\n\tthrow new Error(`${argv[0]} did not report a tunnel URL within ${READY_TIMEOUT_MS / 1000}s`);\n}\n\nfunction processExposure(kind: ExposureKind, baseUrl: string, proc: Bun.Subprocess): ActiveExposure {\n\tproc.unref();\n\treturn {\n\t\tkind,\n\t\tbaseUrl,\n\t\texited: proc.exited.then(() => undefined),\n\t\tstop: () => killTunnelProcess(proc),\n\t};\n}\n\n/**\n * Keep an authenticated Pinggy tunnel behind its configured stable hostname.\n * Random-hostname modes deliberately return their child exit to the broker:\n * restarting those would silently invalidate every already-published URL.\n */\nfunction restartingPinggyExposure(baseUrl: string, argv: string[], initialProc: Bun.Subprocess): ActiveExposure {","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/exposure.ts#L266-L302","documentation":"spawnUrlTunnel waits up to READY_TIMEOUT_MS (30 seconds) for the tunnel binary to print a line matching the ready pattern (e.g. a trycloudflare.com URL). If the process stays alive the whole time but never emits a recognizable URL, the child is killed and this timeout error is thrown. It means the tunnel did not become ready in time or its output format is not understood by the parser.","triggerScenarios":"startExposure with a tunnel kind where the spawned binary runs for 30+ seconds without printing a line the adapter's readyPattern/parser matches — slow tunnel registration, hung network, or output format changed in a newer binary version.","commonSituations":"Very slow networks or DNS delays exceeding 30s; cloudflared stuck retrying connections; ngrok interactive prompt waiting on stdin; a newer tunnel binary that changed log format so the parser never matches; tailscale funnel requiring login in a browser.","solutions":["Run the tunnel binary manually with the same argv and inspect its output format; update the parser/ready pattern if the format changed.","Increase READY_TIMEOUT_MS if the environment is simply slow.","Pre-authenticate the binary so it never blocks on prompts (e.g. `ngrok config add-authtoken`, `cloudflared tunnel login`, `tailscale up`).","Check connectivity to the tunnel service from this host.","Choose a different exposure kind (ssh, direct) that does not depend on this service."],"exampleFix":"// before\nawait spawnUrlTunnel([binary, \"tunnel\"], parseCloudflaredUrl); // hangs on unauthenticated cloudflared\n// after\n// pre-authenticate outside the hot path\nawait $`cloudflared tunnel login`;\nawait spawnUrlTunnel([binary, \"tunnel\"], parseCloudflaredUrl);","handlingStrategy":"retry","validationCode":"const probe = Bun.spawnSync([binary, ...argv.slice(1)], { stdout: \"pipe\", stderr: \"pipe\", timeout: 10_000 });\n// run once manually and confirm output contains an https:// URL the parser would match","typeGuard":null,"tryCatchPattern":"try {\n  const exposure = await startExposure(config);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"did not report a tunnel URL\")) {\n    await Bun.sleep(1_000);\n    return retryStartExposure(config, 2); // bounded retry for slow networks\n  }\n  throw err;\n}","preventionTips":["Pre-authenticate so the binary never blocks on interactive prompts","Check the installed binary's log format against the parser expectations","Allow >30s for slow networks or raise the timeout where configurable","Kill stale tunnel processes that may hold sessions"],"tags":["tunnel","timeout","parsing","network"],"backgroundTag":"tunnel-ready-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}