aio-libs/aiohttp · error · RuntimeError

OPTIONS route was set already

Error message

OPTIONS route was set already

What it means

Raised as RuntimeError by StaticResource.set_options_route when an OPTIONS route has already been registered on that static resource. The Application calls set_options_route automatically to add automatic OPTIONS handling; calling it again (or the app doing so twice) indicates a double-setup that would conflict.

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 c0ef574e29)

Solutions

  1. Do not call set_options_route manually on a StaticResource — the Application handles it.
  2. Ensure Application.freeze()/startup runs only once per instance; create a fresh Application per test/process.
  3. If you need custom OPTIONS behavior, register an explicit OPTIONS route via add_route instead of set_options_route.

Example fix

# before
resource.set_options_route(my_handler)  # app already set it

# after
# omit the manual call; let Application handle OPTIONS automatically
app.router.add_route('OPTIONS', '/static/{tail:.*}', my_handler)  # if custom needed
Defensive patterns

Strategy: validation

Validate before calling

def guard_options(resource):
    if 'OPTIONS' in getattr(resource, '_routes', {}):
        raise RuntimeError('OPTIONS route already set on this resource')
    return resource

Try / catch

try:
    resource.set_options_route(handler)
except RuntimeError as e:
    if 'OPTIONS route was set already' in str(e):
        log.debug('OPTIONS already configured, skipping')
    else:
        raise

Prevention

When it happens

Trigger: Internally invoked by Application setup to add automatic OPTIONS support to a static resource; raised if setup runs twice on the same StaticResource, or if user code calls resource.set_options_route(handler) after the Application already did.

Common situations: Manually calling set_options_route on a static resource that the Application has already configured; re-freezing/re-starting the same Application; duplicate setup paths in test fixtures.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/e3dd3b89ca1119a8.json. Report an issue: GitHub.