{"record":{"id":"9517201d3dca9936","repo":"can1357/oh-my-pi","slug":"imageurls-exposure-named-cloudflared-requires-im","errorCode":null,"errorMessage":"imageUrls exposure \"named-cloudflared\" requires imageUrls.publicBaseUrl","messagePattern":"imageUrls exposure \"named-cloudflared\" requires imageUrls\\.publicBaseUrl","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/exposure.ts","lineNumber":462,"sourceCode":"\t\t\tconst { proc, baseUrl } = await spawnUrlTunnel(\n\t\t\t\t[binary, \"share\", \"public\", `http://127.0.0.1:${port}`, \"--headless\", \"--backend-mode\", \"proxy\"],\n\t\t\t\tparseZrokUrl,\n\t\t\t);\n\t\t\treturn processExposure(\"zrok\", baseUrl, proc);\n\t\t}\n\t\tcase \"bore\": {\n\t\t\tconst binary = requireBinary(\"bore\");\n\t\t\tconst server = optionString(config, \"server\", \"bore.pub\");\n\t\t\tif (!server) throw new Error('imageUrls exposure \"bore\" requires options.server');\n\t\t\tconst secret = credentialString(config, \"secret\");\n\t\t\tconst argv = [binary, \"local\", String(port), \"--to\", server];\n\t\t\tif (secret) argv.push(\"--secret\", secret);\n\t\t\tconst { proc, baseUrl } = await spawnUrlTunnel(argv, line => parseBoreUrl(line, server));\n\t\t\treturn processExposure(\"bore\", baseUrl, proc);\n\t\t}\n\t\tcase \"named-cloudflared\": {\n\t\t\tif (!config.publicBaseUrl) {\n\t\t\t\tthrow new Error('imageUrls exposure \"named-cloudflared\" requires imageUrls.publicBaseUrl');\n\t\t\t}\n\t\t\tconst binary = requireBinary(\"cloudflared\");\n\t\t\tconst token = credentialString(config, \"tunnelToken\");\n\t\t\tlet argv: string[];\n\t\t\tif (token) {\n\t\t\t\targv = [binary, \"tunnel\", \"--no-autoupdate\", \"run\", \"--token\", token];\n\t\t\t} else {\n\t\t\t\tconst configFile = optionString(config, \"configFile\");\n\t\t\t\tconst tunnelName = optionString(config, \"tunnelName\");\n\t\t\t\tif (!configFile || !tunnelName) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'imageUrls exposure \"named-cloudflared\" requires credentials.tunnelToken or options.configFile and options.tunnelName',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\targv = [binary, \"tunnel\", \"--no-autoupdate\", \"--config\", configFile, \"run\", tunnelName];\n\t\t\t}\n\t\t\tconst baseUrl = normalizeBaseUrl(config.publicBaseUrl);\n\t\t\tconst { proc } = await spawnUrlTunnel(","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/exposure.ts#L444-L480","documentation":"The named-cloudflared exposure runs a Cloudflare Tunnel for a hostname you already own, so omp needs to know the public base URL that hostname serves. This error is thrown when config.publicBaseUrl is missing for kind \"named-cloudflared\". Unlike quick cloudflared, the URL cannot be discovered from output — it must be configured.","triggerScenarios":"Selecting exposure kind \"named-cloudflared\" in imageUrls config without setting imageUrls.publicBaseUrl.","commonSituations":"User set up a Cloudflare named tunnel but forgot to mirror its public hostname into imageUrls.publicBaseUrl; migrated from \"cloudflared\" (which needs no publicBaseUrl) to \"named-cloudflared\" without adding the field.","solutions":["Add imageUrls.publicBaseUrl with the tunnel's public origin, e.g. \"https://files.example.com\" (no trailing slash).","Create the Cloudflare named tunnel and route its hostname first if it does not exist yet.","Fall back to kind \"cloudflared\" (quick tunnel) if you don't own a hostname."],"exampleFix":"// before\n\"imageUrls\": { \"exposure\": { \"kind\": \"named-cloudflared\" } }\n// after\n\"imageUrls\": { \"publicBaseUrl\": \"https://files.example.com\", \"exposure\": { \"kind\": \"named-cloudflared\" } }","handlingStrategy":"validation","validationCode":"if (config.kind === \"named-cloudflared\") {\n  const base = config.publicBaseUrl;\n  if (typeof base !== \"string\" || !/^https?:\\/\\/[^/]+$/.test(base)) {\n    throw new Error(\"named-cloudflared requires publicBaseUrl like https://files.example.com\");\n  }\n}","typeGuard":"function hasPublicBaseUrl(c: { kind: string; publicBaseUrl?: string }): c is typeof c & { publicBaseUrl: string } {\n  return c.kind !== \"named-cloudflared\" || (typeof c.publicBaseUrl === \"string\" && c.publicBaseUrl.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Always set publicBaseUrl alongside named-cloudflared","Validate the full exposure config block at config-load time","Remember: quick \"cloudflared\" needs no URL, \"named-cloudflared\" and \"ssh\" do"],"tags":["configuration","cloudflared","missing-required-option"],"backgroundTag":"missing-required-option","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}