JuliusBrussee/caveman · error · Error

invalid CAVEMAN_LISTEN

Error message

invalid CAVEMAN_LISTEN ${JSON.stringify(listen)}; expected host:port

What it means

Thrown by standaloneProxyEndpoint() when the CAVEMAN_LISTEN environment variable cannot be interpreted as a host:port pair needed to bind the standalone Caveman proxy. The message includes the offending value JSON-quoted and states the expected 'host:port' format. It is the user-facing form of the internal 'invalid listener' guard.

Solutions

  1. Set CAVEMAN_LISTEN to exactly host:port, e.g. CAVEMAN_LISTEN=0.0.0.0:8080.
  2. Strip scheme, path, and whitespace from the value.
  3. Use a valid port number (1-65535) that is not privileged (>1024 unless root).
  4. Unset CAVEMAN_LISTEN to use the default listener address.

Example fix

// before
export CAVEMAN_LISTEN="http://localhost:8080"
caveman proxy   # throws invalid CAVEMAN_LISTEN "http://localhost:8080"; expected host:port
// after
export CAVEMAN_LISTEN="localhost:8080"
caveman proxy
Defensive patterns

Strategy: validation

Validate before calling

const listen = process.env.CAVEMAN_LISTEN;
if (listen && !/^[-.\w]+:(\d{1,5})$/.test(listen.trim()) || (listen && !(+listen.split(":")[1] >= 1 && +listen.split(":")[1] <= 65535))) {
  throw new Error(`CAVEMAN_LISTEN "${listen}" is not host:port (e.g. 127.0.0.1:8080)`);
}

Try / catch

try {
  runProxy();
} catch (e) {
  if (e instanceof Error && e.message.startsWith("invalid CAVEMAN_LISTEN")) {
    console.error(e.message, "— expected e.g. CAVEMAN_LISTEN=127.0.0.1:8080");
  } else throw e;
}

Prevention

When it happens

Trigger: CAVEMAN_LISTEN is set (or defaults to PROXY_ADDR) and fails URL-based validation: missing hostname, port not an integer, port outside 1-65535, or a path suffix; or `new URL('http://' + listen)` itself throws.

Common situations: typos like CAVEMAN_LISTEN="localhost 8080", "localhost:", ":8080" (empty hostname), "localhost:8080/", quoted values with stray spaces, or a full URL pasted in instead of host:port.

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

Appendix: source

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

  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;
  const here = fileURLToPath(import.meta.url);
  try {
    return realpathSync(process.argv[1]) === here;

View on GitHub (pinned to 3ee70a1026)