JuliusBrussee/caveman · error
cave_gateway_identity_unverified
cave_gateway_identity_unverified
Error message
cave_gateway_identity_unverified: non-loopback gateway ${gatewayURL} requires https What it means
Thrown during gateway identity verification when the gateway URL is non-loopback and uses plain http. Routing to a host sends the provider credential plus every x-cave-* header, so a non-loopback gateway must prove Caveman identity first — and http cannot authenticate the peer at all. The check fails closed: without a plan the run degrades to observe-only; with a locked plan it surfaces wrapped as cave_gateway_required_for_locked_plan.
Source
Thrown at packages/agent/src/runtime.ts:5221
});
return probe;
}
export async function ensureCaveRuntime(
gatewayURL: string,
fetchImpl: typeof globalThis.fetch,
): Promise<GatewayProviderBilling> {
const url = new URL(gatewayURL);
const loopback = isLoopbackHostname(url.hostname);
if (!loopback) {
// A non-loopback gateway is never taken on faith: routing there sends the
// provider credential plus every x-cave-* header to that host, so it must
// prove Caveman identity before it gets any traffic. Plain http cannot
// authenticate the peer at all, and an https host that fails the
// /health/ready identity handshake is equally unverified — both fail
// closed here so resolveCaveRoute degrades to observe-only passthrough.
if (url.protocol !== "https:") {
throw new Error(
`cave_gateway_identity_unverified: non-loopback gateway ${gatewayURL} requires https`,
);
}
const identity = await gatewayIdentity(gatewayURL, fetchImpl);
if (identity !== undefined) return identity.providerBilling;
throw new Error(
`cave_gateway_identity_unverified: ${gatewayURL}/health/ready did not identify as caveman-proxy`,
);
}
const readyBilling = await runtimeReady(gatewayURL, fetchImpl);
if (readyBilling !== undefined) return readyBilling;
const command = process.env.CAVEMAN_CLI_BIN ?? "caveman";
let startupFailure: Error | undefined;
let invocation;
try {
invocation = portableInvocation(command, ["start"], { env: buildRuntimeControlEnv() });
} catch (error) {View on GitHub (pinned to 766dce6b13)
Solutions
- Use an https:// URL for any non-loopback gateway (terminate TLS at the gateway or a reverse proxy in front of it)
- For pure local development, point the gateway URL at a loopback hostname (127.0.0.1 / localhost) — loopback is exempt from the https requirement
- Verify the URL actually typed/parsed: a missing scheme defaulting to http:, or a proxy env var rewriting the URL, hits the same check
Example fix
// before const gateway = 'http://cave.internal.corp:8080'; // non-loopback, plain http // after const gateway = 'https://cave.internal.corp:8443'; // or 'http://127.0.0.1:8080' for loopback dev
Defensive patterns
Strategy: validation
Validate before calling
const LOOPBACK = new Set(['127.0.0.1', '::1', 'localhost']);
function validateGatewayURL(gatewayURL) {
const url = new URL(gatewayURL); // throws on malformed URL too
if (!LOOPBACK.has(url.hostname) && url.protocol !== 'https:') {
throw new Error(`non-loopback gateway must use https: ${gatewayURL}`);
}
return url;
} Type guard
function isLoopbackHostname(hostname) {
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
} Try / catch
try {
await run(agent, options);
} catch (error) {
if (error instanceof Error && error.message.includes('requires https')) {
options.caveRoute = { ...options.caveRoute, gatewayURL: toHttps(options.caveRoute.gatewayURL) };
return run(agent, options);
}
throw error;
} Prevention
- Validate the gateway URL scheme at config load time, not at run time
- Terminate TLS at (or in front of) any remote gateway; never route provider credentials over plain http
- For local dev prefer http://127.0.0.1:port which is exempt from the https requirement
When it happens
Trigger: ensureCaveRuntime / gatewayIdentity called with a gatewayURL whose hostname is not loopback (LAN IP, domain, 0.0.0.0, docker host name) and whose protocol is not https: — e.g. http://10.0.0.5:8080 or http://cave.internal:80.
Common situations: Teams pointing the framework at a shared/internal gateway over plain http on the LAN; local docker setups where the gateway is reached via the docker host name instead of 127.0.0.1; a reverse proxy terminating TLS but the configured upstream URL kept as http.
Related errors
- objectstore: production requires TLS
- postgres: production DATABASE_URL requires sslmode=verify-fu
- postgres: production connection includes a plaintext fallbac
- cave_live_eval_sandbox_profile_escapes_root
- caveman-code: path escapes the workspace: ${candidate}
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/036c640dad80319c.
Report an issue: GitHub.