JuliusBrussee/caveman · error · ValueError

Configure exact POST paths and native LLM protocols

Error message

Configure exact POST paths and native LLM protocols

What it means

CavemanASGIMiddleware validates its `routes` mapping at construction. The middleware only projects exact, literal POST paths onto native LLM wire protocols (openai-chat, openai-responses, anthropic-messages); wildcards, query strings, relative paths, non-string keys, unknown protocol names, or an empty mapping are rejected. This fails fast so misconfigured routing never silently bypasses or misroutes LLM traffic at runtime.

Solutions

  1. Use exact literal paths starting with '/', one per native endpoint (e.g. '/v1/chat/completions', '/v1/messages'), with no '*' or '?' characters
  2. Use exactly one of the protocol strings: 'openai-chat', 'openai-responses', 'anthropic-messages'
  3. Ensure routes is a non-empty Mapping[str, Protocol] and every key is a str (not bytes or None)
  4. Remove any query strings from paths; query parameters are part of the scope, not the route key

Example fix

// before
middleware = CavemanASGIMiddleware(app, runtime=rt, routes={'/v1/*': 'openai-chat'}, resolve_context=ctx)
// after
middleware = CavemanASGIMiddleware(app, runtime=rt, routes={'/v1/chat/completions': 'openai-chat'}, resolve_context=ctx)
Defensive patterns

Strategy: validation

Validate before calling

VALID_PROTOCOLS = {'openai-chat', 'openai-responses', 'anthropic-messages'}
assert routes, 'routes must be non-empty'
for path, protocol in routes.items():
    assert isinstance(path, str) and path.startswith('/'), f'bad path: {path!r}'
    assert '*' not in path and '?' not in path, f'non-exact path: {path!r}'
    assert protocol in VALID_PROTOCOLS, f'bad protocol: {protocol!r}'

Type guard

def is_valid_routes(routes) -> bool:
    return bool(routes) and all(
        isinstance(p, str) and p.startswith('/') and '*' not in p and '?' not in p
        and proto in ('openai-chat', 'openai-responses', 'anthropic-messages')
        for p, proto in routes.items())

Prevention

When it happens

Trigger: Instantiating CavemanASGIMiddleware with: an empty routes dict; a path key that is not a str; a path not starting with '/'; a path containing '*' or '?' (glob/query style); or a protocol value other than exactly 'openai-chat', 'openai-responses', or 'anthropic-messages' (e.g. 'chat/completions', 'OpenAI-Chat', 'openai_chat').

Common situations: Developers copy glob route patterns from web frameworks ('/v1/*'), append query strings ('/v1/chat/completions?model=x'), typo a protocol name, use an internal enum/string from their own config layer, or pass routes=None/empty when disabling routes via feature flags.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/asgi.py:68

    raise ValueError("non-finite JSON")


class CavemanASGIMiddleware:
    """Place inside authentication and original-content guard middleware.

    resolve_context may decline by returning None; existing application routing
    and auth then run unchanged. It must use authenticated server state, not
    namespace/session headers. This middleware never handles an auth rejection.
    """
    def __init__(self, app, *, runtime: AsyncMiddlewareRuntime,
                 routes: Mapping[str, Protocol],
                 resolve_context: Callable[[ASGIScope], ASGIContext | None | Awaitable[ASGIContext | None]],
                 max_body_bytes: int = 2 << 20, max_request_chunks: int = 256):
        if not isinstance(runtime, AsyncMiddlewareRuntime):
            raise TypeError("ASGI requires AsyncMiddlewareRuntime")
        if not routes or any(not isinstance(path, str) or not path.startswith("/") or "*" in path or "?" in path
                             or protocol not in ("openai-chat", "openai-responses", "anthropic-messages") for path, protocol in routes.items()):
            raise ValueError("Configure exact POST paths and native LLM protocols")
        if not callable(resolve_context) or not 1 <= max_body_bytes <= 2 << 20 or not 1 <= max_request_chunks <= 4096:
            raise ValueError("ASGI context resolver or request bounds are invalid")
        self.app, self.runtime = app, runtime
        self.routes, self.resolve_context = dict(routes), resolve_context
        self.max_body_bytes, self.max_request_chunks = max_body_bytes, max_request_chunks
        self._version_supported = matches_framework(("fastapi", "0.141", "1"), ("starlette", "1.6", "2"))
        if not self._version_supported and runtime.mode != "off":
            runtime.decline("unsupported_version")

    async def __call__(self, scope, receive, send):
        if owner.get() is not None:
            return await self.app(scope, receive, send)

        protocol = self.routes.get(scope.get("path"))
        async def passthrough(reader, reason):
            if scope.get("type") != "http" or scope.get("method") != "POST" or protocol is None:
                self.runtime.report(None, reason=reason, adapter="asgi")
                return await self.app(scope, reader, send)

View on GitHub (pinned to 3ee70a1026)