{"record":{"id":"c4518612b1bdfb1e","repo":"can1357/oh-my-pi","slug":"imageurls-exposure-bore-requires-options-server","errorCode":null,"errorMessage":"imageUrls exposure \"bore\" requires options.server","messagePattern":"imageUrls exposure \"bore\" requires options\\.server","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/exposure.ts","lineNumber":453,"sourceCode":"\t\t\tconst binary = requireBinary(\"devtunnel\");\n\t\t\tconst { proc, baseUrl } = await spawnUrlTunnel(\n\t\t\t\t[binary, \"host\", \"-p\", String(port), \"--allow-anonymous\", \"--protocol\", \"http\"],\n\t\t\t\tparseDevtunnelUrl,\n\t\t\t);\n\t\t\treturn processExposure(\"devtunnel\", baseUrl, proc);\n\t\t}\n\t\tcase \"zrok\": {\n\t\t\tconst binary = requireBinary(\"zrok\");\n\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\");","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/exposure.ts#L435-L471","documentation":"The bore exposure adapter requires a `server` option naming the bore relay host (defaulting to \"bore.pub\"). This guard throws when the resolved server option is present-but-empty (optionString returned an empty string), since a bore client cannot connect to a blank relay address. It is a configuration validation error before any process is spawned.","triggerScenarios":"Configuring imageUrls exposure kind \"bore\" with `options.server` set to an empty string; optionString's default only applies when the key is missing, not when it is explicitly empty.","commonSituations":"A config file has `server = \"\"` or an env var interpolation that resolves to empty; a settings UI saved an empty field instead of clearing the key.","solutions":["Set options.server to a valid bore relay host, or remove the key entirely to use the \"bore.pub\" default.","Validate the config before saving: reject empty strings for server.","Run your own bore server and point server at it if bore.pub is unreliable."],"exampleFix":"// before\n\"bore\": { \"options\": { \"server\": \"\" } }\n// after\n\"bore\": { \"options\": { \"server\": \"bore.pub\" } }","handlingStrategy":"validation","validationCode":"if (config.kind === \"bore\") {\n  const server = config.options.server ?? \"bore.pub\";\n  if (typeof server !== \"string\" || server.trim() === \"\") {\n    throw new Error(\"bore exposure: options.server must be a non-empty host\");\n  }\n}","typeGuard":"function hasBoreServer(o: unknown): o is { server: string } {\n  return typeof o === \"object\" && o !== null && \"server\" in o &&\n    typeof (o as { server: unknown }).server === \"string\" &&\n    (o as { server: string }).server.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Omit the server key instead of setting it to an empty string","Validate config at load time: no empty-string options","Use the built-in \"bore.pub\" default unless running your own relay"],"tags":["configuration","bore","validation"],"backgroundTag":"missing-required-option","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}