JuliusBrussee/caveman · error · MiddlewareError
invalid_endpoint
invalid_endpoint
Error message
invalid_endpoint
What it means
The middleware Runtime constructor validates the endpoint option as a strict HTTP(S) origin URL and throws MiddlewareError with code 'invalid_endpoint' if it fails any check: protocol must be http: or https:, no embedded username/password, no query string, no hash, and the pathname must be '/' or empty. The default is http://127.0.0.1:8787.
Solutions
- Pass a bare origin: new Runtime({ endpoint: 'http://127.0.0.1:8787' }) — no path, query, hash, or credentials
- Prepend the scheme if missing (https:// or http://)
- Strip any path/query/fragment from the configured endpoint string before constructing Runtime
- Validate the endpoint with the same URL checks in your config-loading layer and fail with a clear message
Example fix
// before
const rt = new Runtime({ endpoint: process.env.CAVE_MW_URL }); // 'http://host:8787/api'
// after
const raw = new URL(process.env.CAVE_MW_URL);
const rt = new Runtime({ endpoint: raw.origin }); // 'http://host:8787' Defensive patterns
Strategy: validation
Validate before calling
function sanitizeEndpoint(raw: string): string {
const u = new URL(raw);
if (!['http:', 'https:'].includes(u.protocol) || u.username || u.password || u.search || u.hash || (u.pathname !== '/' && u.pathname !== '')) throw new Error(`endpoint must be a bare origin, got: ${raw}`);
return u.origin;
} Type guard
function isBareHttpOrigin(raw: string): boolean { try { const u = new URL(raw); return ['http:', 'https:'].includes(u.protocol) && !u.username && !u.password && !u.search && !u.hash && (u.pathname === '/' || u.pathname === ''); } catch { return false; } } Try / catch
try { const rt = new Runtime({ endpoint }); } catch (e) { if (e instanceof MiddlewareError && e.code === 'invalid_endpoint') throw new Error(`CAVE_MW_URL must be a bare http(s) origin, got '${endpoint}'`); throw e; } Prevention
- Store endpoints as bare origins (scheme://host:port) in config/env
- Trim and normalize endpoint strings at config load time
- Never append paths like /v1 or /api to middleware endpoints
- Validate with new URL() before passing to Runtime so failures surface in config parsing
When it happens
Trigger: new Runtime({ endpoint: '127.0.0.1:8787' }) (missing scheme), 'http://127.0.0.1:8787/api' (non-root path), 'http://user:pass@host/', 'https://host/?x=1' or 'https://host/#frag', or 'ftp://host/'. Also occurs when the endpoint string comes from an env var with a trailing path or whitespace-encoded characters that produce a non-empty pathname/query.
Common situations: Copying a full API URL (with path) into the endpoint config instead of a bare origin; forgetting the https:// scheme; config templates that append /v1 or /middleware to the endpoint; URL env vars with trailing '?' or '#' characters.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- invalid_endpoint
- provider upstream URL scheme is not allowed
- bedrock base url invalid
- cave_breaker_retry_backoff_invalid
- cave_breaker_retry_requires_budget
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/3dbd4731272a60d7.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/middleware/runtime.ts:95
readonly mode: 'off' | 'record' | 'compress';
private readonly options: RuntimeOptions;
private readonly fetcher: typeof globalThis.fetch;
private readonly lifetime = new AbortController();
private readonly bindings = new WeakSet<RecoveryBinding>();
private readonly capsCache: { value: Capabilities | null } = { value: null };
private failures = 0;
private openUntil = 0;
private pending = 0;
private receiptsPending = 0;
private fetchesPending = 0;
private receiptFetchesPending = 0;
private receiptTail: Promise<void> = Promise.resolve();
private reported: CallReport | null = null;
constructor(options: RuntimeOptions = {}) {
const url = new URL(options.endpoint ?? 'http://127.0.0.1:8787');
const local = ['127.0.0.1','[::1]','localhost'].includes(url.hostname);
if (!['http:','https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || (url.pathname !== '/' && url.pathname !== '')) throw new MiddlewareError('invalid_endpoint');
if (!local && (!options.allowRemoteContent || url.protocol !== 'https:')) throw new MiddlewareError('remote_content_not_enabled');
for (const value of [options.deadlineMs, options.retrieveDeadlineMs]) {
if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) throw new MiddlewareError('invalid_deadline');
}
this.endpoint = url.origin;
this.options = { ...options };
this.mode = options.mode ?? 'compress';
this.fetcher = options.fetch ?? globalThis.fetch;
}
/** Prime capability discovery during app startup, outside the first model call. */
async ready(signal?: AbortSignal): Promise<Capabilities> {
signal?.throwIfAborted();
if (this.mode === 'off') throw new MiddlewareError('off');
const value = validateCapabilities(await this.http('capabilities', undefined, this.options.deadlineMs ?? 100, signal));
this.capsCache.value = value;
return value;
}View on GitHub (pinned to 3ee70a1026)