can1357/oh-my-pi · error

imageUrls exposure "bore" requires options.server

Error message

imageUrls exposure "bore" requires options.server

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/exposure.ts:453

			const binary = requireBinary("devtunnel");
			const { proc, baseUrl } = await spawnUrlTunnel(
				[binary, "host", "-p", String(port), "--allow-anonymous", "--protocol", "http"],
				parseDevtunnelUrl,
			);
			return processExposure("devtunnel", baseUrl, proc);
		}
		case "zrok": {
			const binary = requireBinary("zrok");
			const { proc, baseUrl } = await spawnUrlTunnel(
				[binary, "share", "public", `http://127.0.0.1:${port}`, "--headless", "--backend-mode", "proxy"],
				parseZrokUrl,
			);
			return processExposure("zrok", baseUrl, proc);
		}
		case "bore": {
			const binary = requireBinary("bore");
			const server = optionString(config, "server", "bore.pub");
			if (!server) throw new Error('imageUrls exposure "bore" requires options.server');
			const secret = credentialString(config, "secret");
			const argv = [binary, "local", String(port), "--to", server];
			if (secret) argv.push("--secret", secret);
			const { proc, baseUrl } = await spawnUrlTunnel(argv, line => parseBoreUrl(line, server));
			return processExposure("bore", baseUrl, proc);
		}
		case "named-cloudflared": {
			if (!config.publicBaseUrl) {
				throw new Error('imageUrls exposure "named-cloudflared" requires imageUrls.publicBaseUrl');
			}
			const binary = requireBinary("cloudflared");
			const token = credentialString(config, "tunnelToken");
			let argv: string[];
			if (token) {
				argv = [binary, "tunnel", "--no-autoupdate", "run", "--token", token];
			} else {
				const configFile = optionString(config, "configFile");
				const tunnelName = optionString(config, "tunnelName");

View on GitHub (pinned to 9690622007)

Solutions

  1. Set options.server to a valid bore relay host, or remove the key entirely to use the "bore.pub" default.
  2. Validate the config before saving: reject empty strings for server.
  3. Run your own bore server and point server at it if bore.pub is unreliable.

Example fix

// before
"bore": { "options": { "server": "" } }
// after
"bore": { "options": { "server": "bore.pub" } }
Defensive patterns

Strategy: validation

Validate before calling

if (config.kind === "bore") {
  const server = config.options.server ?? "bore.pub";
  if (typeof server !== "string" || server.trim() === "") {
    throw new Error("bore exposure: options.server must be a non-empty host");
  }
}

Type guard

function hasBoreServer(o: unknown): o is { server: string } {
  return typeof o === "object" && o !== null && "server" in o &&
    typeof (o as { server: unknown }).server === "string" &&
    (o as { server: string }).server.trim().length > 0;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/c4518612b1bdfb1e. Report an issue: GitHub.