can1357/oh-my-pi · error

imageUrls exposure "named-cloudflared" requires imageUrls.pu

Error message

imageUrls exposure "named-cloudflared" requires imageUrls.publicBaseUrl

What it means

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.

Source

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

			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");
				if (!configFile || !tunnelName) {
					throw new Error(
						'imageUrls exposure "named-cloudflared" requires credentials.tunnelToken or options.configFile and options.tunnelName',
					);
				}
				argv = [binary, "tunnel", "--no-autoupdate", "--config", configFile, "run", tunnelName];
			}
			const baseUrl = normalizeBaseUrl(config.publicBaseUrl);
			const { proc } = await spawnUrlTunnel(

View on GitHub (pinned to 9690622007)

Solutions

  1. Add imageUrls.publicBaseUrl with the tunnel's public origin, e.g. "https://files.example.com" (no trailing slash).
  2. Create the Cloudflare named tunnel and route its hostname first if it does not exist yet.
  3. Fall back to kind "cloudflared" (quick tunnel) if you don't own a hostname.

Example fix

// before
"imageUrls": { "exposure": { "kind": "named-cloudflared" } }
// after
"imageUrls": { "publicBaseUrl": "https://files.example.com", "exposure": { "kind": "named-cloudflared" } }
Defensive patterns

Strategy: validation

Validate before calling

if (config.kind === "named-cloudflared") {
  const base = config.publicBaseUrl;
  if (typeof base !== "string" || !/^https?:\/\/[^/]+$/.test(base)) {
    throw new Error("named-cloudflared requires publicBaseUrl like https://files.example.com");
  }
}

Type guard

function hasPublicBaseUrl(c: { kind: string; publicBaseUrl?: string }): c is typeof c & { publicBaseUrl: string } {
  return c.kind !== "named-cloudflared" || (typeof c.publicBaseUrl === "string" && c.publicBaseUrl.length > 0);
}

Prevention

When it happens

Trigger: Selecting exposure kind "named-cloudflared" in imageUrls config without setting imageUrls.publicBaseUrl.

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

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/9517201d3dca9936. Report an issue: GitHub.