{"record":{"id":"e3dd3b89ca1119a8","repo":"aio-libs/aiohttp","slug":"options-route-was-set-already","errorCode":null,"errorMessage":"OPTIONS route was set already","messagePattern":"OPTIONS route was set already","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":593,"sourceCode":"        return url\n\n    @staticmethod\n    def _get_file_hash(byte_array: bytes) -> str:\n        m = hashlib.sha256()  # todo sha256 can be configurable param\n        m.update(byte_array)\n        b64 = base64.urlsafe_b64encode(m.digest())\n        return b64.decode(\"ascii\")\n\n    def get_info(self) -> _InfoDict:\n        return {\n            \"directory\": self._directory,\n            \"prefix\": self._prefix,\n            \"routes\": self._routes,\n        }\n\n    def set_options_route(self, handler: Handler) -> \"ResourceRoute\":\n        if \"OPTIONS\" in self._routes:\n            raise RuntimeError(\"OPTIONS route was set already\")\n        route = ResourceRoute(\n            \"OPTIONS\", handler, self, expect_handler=self._expect_handler\n        )\n        self._routes[\"OPTIONS\"] = route\n        self._allowed_methods.add(\"OPTIONS\")\n        return route\n\n    async def resolve(self, request: Request) -> _Resolve:\n        path = request.rel_url.path_safe\n        method = request.method\n        # We normalise here to avoid matches that traverse below the static root.\n        # e.g. /static/../../../../home/user/webapp/static/\n        norm_path = os.path.normpath(path)\n        if IS_WINDOWS:\n            norm_path = norm_path.replace(\"\\\\\", \"/\")\n        if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:\n            return None, set()\n","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L575-L611","documentation":"StaticResource.set_options_route registers an OPTIONS handler for the static resource and refuses to do so twice. The first call adds 'OPTIONS' to self._routes; a second call detects the existing key and raises RuntimeError. This prevents silently replacing the OPTIONS handler, which would hide a configuration mistake (e.g. CORS middleware double-registering).","triggerScenarios":"Calling set_options_route(handler) twice on the same StaticResource. In practice this is usually invoked automatically by aiohttp's CORS or OPTIONS handling machinery; user code rarely calls it directly. The duplicate call indicates two components both trying to set the OPTIONS route.","commonSituations":"Two CORS/OPTIONS middleware both attempting to register an OPTIONS route on the same static resource; a plugin and user code both calling set_options_route; re-initialization during hot reload without recreating the resource.","solutions":["Ensure set_options_route is called at most once per StaticResource — coordinate between middleware.","If you use a CORS library, let it own OPTIONS handling and do not also call set_options_route manually.","Recreate the resource/router if you need to reconfigure OPTIONS handling."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"def safe_set_options(resource, handler):\n    if 'OPTIONS' in getattr(resource, '_routes', {}):\n        raise RuntimeError('OPTIONS route already set')\n    return resource.set_options_route(handler)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Let a single component (CORS lib or manual code) own OPTIONS handling.","Document which layer registers OPTIONS to avoid conflicts.","Recreate the resource rather than re-registering on hot reload."],"tags":["static-files","options","duplicate","configuration"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}