JuliusBrussee/caveman · error · Error

invalid listener

Error message

invalid listener

What it means

Internal sentinel thrown inside standaloneProxyEndpoint() when CAVEMAN_LISTEN (or the default PROXY_ADDR) does not parse as a valid host:port listener. The outer catch immediately converts it into the more descriptive 'invalid CAVEMAN_LISTEN ... expected host:port' error (index 555), so users normally never see this bare message. It validates hostname presence, integer port in 1-65535, and no path component.

Solutions

  1. Set CAVEMAN_LISTEN to a plain host:port, e.g. CAVEMAN_LISTEN=127.0.0.1:8080.
  2. Remove any path or scheme from the value (no 'localhost:8080/api', no 'http://...').
  3. Use a port between 1 and 65535.
  4. Unset CAVEMAN_LISTEN to fall back to the built-in default PROXY_ADDR.

Example fix

// before
CAVEMAN_LISTEN=localhost:99999 caveman proxy
// after
CAVEMAN_LISTEN=127.0.0.1:8080 caveman proxy
Defensive patterns

Strategy: validation

Validate before calling

function isValidListen(v: string | undefined): boolean {
  if (!v) return true; // falls back to default
  try {
    const u = new URL(`http://${v.trim()}`);
    const p = Number(u.port);
    return !!u.hostname && Number.isInteger(p) && p >= 1 && p <= 65535 && u.pathname === "/";
  } catch { return false; }
}
if (!isValidListen(process.env.CAVEMAN_LISTEN)) throw new Error("CAVEMAN_LISTEN must be host:port");

Try / catch

try {
  startStandaloneProxy();
} catch (e) {
  if (e instanceof Error && /invalid (listener|CAVEMAN_LISTEN)/.test(e.message)) {
    console.error("Set CAVEMAN_LISTEN like 127.0.0.1:8080");
  } else throw e;
}

Prevention

When it happens

Trigger: CAVEMAN_LISTEN value, once wrapped as `http://${listen}` and parsed with new URL(), yields: empty hostname, non-integer or out-of-range port (0, 99999), or a non-"/" pathname (e.g. 'host:port/extra' or missing port).

Common situations: Setting CAVEMAN_LISTEN=":8080" variants whose port parses oddly, "localhost:0", "localhost:99999", or "localhost:8080/path" when launching the standalone proxy.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/0c4b3a492edb7fbf. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:19030

// portListening probes a TCP port with a short timeout — used to detect whether
// the proxy is already up, so start/wrap can say so without false positives.
function portListening(host: string, port: number): Promise<boolean> {
  return new Promise((resolve) => {
    const sock = netConnect({ host, port });
    const finish = (v: boolean) => { sock.destroy(); resolve(v); };
    sock.setTimeout(600);
    sock.once("connect", () => finish(true));
    sock.once("timeout", () => finish(false));
    sock.once("error", () => finish(false));
  });
}

function standaloneProxyEndpoint(): { host: string; port: number; listen: string } {
  const listen = (process.env.CAVEMAN_LISTEN ?? PROXY_ADDR).trim();
  try {
    const url = new URL(`http://${listen}`);
    const port = Number(url.port);
    if (!url.hostname || !Number.isInteger(port) || port < 1 || port > 65535 || url.pathname !== "/") throw new Error("invalid listener");
    return { host: url.hostname, port, listen };
  } catch {
    throw new Error(`invalid CAVEMAN_LISTEN ${JSON.stringify(listen)}; expected host:port`);
  }
}

export function gatewayHostPort(gw = gatewayURL()): { host: string; port: number } {
  try {
    const u = new URL(gw);
    const defaultPort = u.protocol === "https:" ? 443 : 80;
    return { host: u.hostname || "127.0.0.1", port: Number(u.port) || defaultPort };
  } catch {
    return { host: "127.0.0.1", port: 8787 };
  }
}

function isCliEntrypoint(): boolean {
  if (!process.argv[1]) return false;

View on GitHub (pinned to 3ee70a1026)