{"record":{"id":"6946dd1aa7a05e86","repo":"JuliusBrussee/caveman","slug":"asgi-requires-asyncmiddlewareruntime","errorCode":null,"errorMessage":"ASGI requires AsyncMiddlewareRuntime","messagePattern":"ASGI requires AsyncMiddlewareRuntime","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/asgi.py","lineNumber":65,"sourceCode":"\n\ndef _reject_constant(_value):\n    raise ValueError(\"non-finite JSON\")\n\n\nclass CavemanASGIMiddleware:\n    \"\"\"Place inside authentication and original-content guard middleware.\n\n    resolve_context may decline by returning None; existing application routing\n    and auth then run unchanged. It must use authenticated server state, not\n    namespace/session headers. This middleware never handles an auth rejection.\n    \"\"\"\n    def __init__(self, app, *, runtime: AsyncMiddlewareRuntime,\n                 routes: Mapping[str, Protocol],\n                 resolve_context: Callable[[ASGIScope], ASGIContext | None | Awaitable[ASGIContext | None]],\n                 max_body_bytes: int = 2 << 20, max_request_chunks: int = 256):\n        if not isinstance(runtime, AsyncMiddlewareRuntime):\n            raise TypeError(\"ASGI requires AsyncMiddlewareRuntime\")\n        if not routes or any(not isinstance(path, str) or not path.startswith(\"/\") or \"*\" in path or \"?\" in path\n                             or protocol not in (\"openai-chat\", \"openai-responses\", \"anthropic-messages\") for path, protocol in routes.items()):\n            raise ValueError(\"Configure exact POST paths and native LLM protocols\")\n        if not callable(resolve_context) or not 1 <= max_body_bytes <= 2 << 20 or not 1 <= max_request_chunks <= 4096:\n            raise ValueError(\"ASGI context resolver or request bounds are invalid\")\n        self.app, self.runtime = app, runtime\n        self.routes, self.resolve_context = dict(routes), resolve_context\n        self.max_body_bytes, self.max_request_chunks = max_body_bytes, max_request_chunks\n        self._version_supported = matches_framework((\"fastapi\", \"0.141\", \"1\"), (\"starlette\", \"1.6\", \"2\"))\n        if not self._version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n\n    async def __call__(self, scope, receive, send):\n        if owner.get() is not None:\n            return await self.app(scope, receive, send)\n\n        protocol = self.routes.get(scope.get(\"path\"))\n        async def passthrough(reader, reason):","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/asgi.py#L47-L83","documentation":"The ASGI middleware intercepts async request/response cycles, so it only accepts an AsyncMiddlewareRuntime. Constructing it with a synchronous MiddlewareRuntime (or any other object) raises TypeError at app startup time.","triggerScenarios":"Passing MiddlewareRuntime(...) as the `runtime` argument of the ASGI wrapper constructor, or passing None/a mock.","commonSituations":"Sharing one sync runtime configured for CLI/scripts into a web app; refactoring from a sync gateway to ASGI (FastAPI/Starlette) without swapping the runtime class.","solutions":["Construct and pass an AsyncMiddlewareRuntime to the ASGI wrapper","If you have a sync runtime, create a separate async runtime instance with the same configuration","Wrap the construction in a startup check that fails fast with a clear message"],"exampleFix":"// before\napp = CavemanASGI(app, runtime=MiddlewareRuntime(...), routes=routes, resolve_context=resolve)\n// after\nfrom caveman_cloud.middleware import AsyncMiddlewareRuntime\napp = CavemanASGI(app, runtime=AsyncMiddlewareRuntime(...), routes=routes, resolve_context=resolve)","handlingStrategy":"type-guard","validationCode":"from caveman_cloud.middleware import AsyncMiddlewareRuntime\nif not isinstance(runtime, AsyncMiddlewareRuntime):\n    raise TypeError('ASGI wrapper requires AsyncMiddlewareRuntime')","typeGuard":"def is_async_runtime(runtime) -> bool:\n    from caveman_cloud.middleware import AsyncMiddlewareRuntime\n    return isinstance(runtime, AsyncMiddlewareRuntime)","tryCatchPattern":"try:\n    asgi = CavemanASGI(app, runtime=runtime, routes=routes, resolve_context=resolve)\nexcept TypeError as e:\n    if 'AsyncMiddlewareRuntime' in str(e):\n        asgi = CavemanASGI(app, runtime=AsyncMiddlewareRuntime.from_config(cfg), routes=routes, resolve_context=resolve)\n    else:\n        raise","preventionTips":["Separate sync runtime (CLI/scripts) from async runtime (web app) configs","Validate runtime kind in a startup hook before mounting the ASGI app","Type-hint the runtime parameter as AsyncMiddlewareRuntime"],"tags":["python","asgi","typeerror","sync-async"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}