aio-libs/aiohttp · error · RuntimeError
OPTIONS route was set already
Error message
OPTIONS route was set already
What it means
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).
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.
Defensive patterns
Strategy: validation
Validate before calling
def safe_set_options(resource, handler):
if 'OPTIONS' in getattr(resource, '_routes', {}):
raise RuntimeError('OPTIONS route already set')
return resource.set_options_route(handler) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ' ' does not exist
- ' ' is not a directory
- 403: Forbidden
- 404: Not Found
- Added route will never be executed, method
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/e3dd3b89ca1119a8.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_urldispatcher.py:593
return url
@staticmethod
def _get_file_hash(byte_array: bytes) -> str:
m = hashlib.sha256() # todo sha256 can be configurable param
m.update(byte_array)
b64 = base64.urlsafe_b64encode(m.digest())
return b64.decode("ascii")
def get_info(self) -> _InfoDict:
return {
"directory": self._directory,
"prefix": self._prefix,
"routes": self._routes,
}
def set_options_route(self, handler: Handler) -> "ResourceRoute":
if "OPTIONS" in self._routes:
raise RuntimeError("OPTIONS route was set already")
route = ResourceRoute(
"OPTIONS", handler, self, expect_handler=self._expect_handler
)
self._routes["OPTIONS"] = route
self._allowed_methods.add("OPTIONS")
return route
async def resolve(self, request: Request) -> _Resolve:
path = request.rel_url.path_safe
method = request.method
# We normalise here to avoid matches that traverse below the static root.
# e.g. /static/../../../../home/user/webapp/static/
norm_path = os.path.normpath(path)
if IS_WINDOWS:
norm_path = norm_path.replace("\\", "/")
if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:
return None, set()
View on GitHub (pinned to d041d4d0fd)