{"record":{"id":"3d973522e53542fd","repo":"aio-libs/aiohttp","slug":"path-should-be-started-with-or-be-empty","errorCode":null,"errorMessage":"path should be started with / or be empty","messagePattern":"path should be started with / or be empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":1105,"sourceCode":"            index_key = index_key.partition(\"{\")[0].rpartition(\"/\")[0]\n        return index_key.rstrip(\"/\") or \"/\"\n\n    def index_resource(self, resource: AbstractResource) -> None:\n        \"\"\"Add a resource to the resource index.\"\"\"\n        resource_key = self._get_resource_index_key(resource)\n        # There may be multiple resources for a canonical path\n        # so we keep them in a list to ensure that registration\n        # order is respected.\n        self._resource_index.setdefault(resource_key, []).append(resource)\n\n    def unindex_resource(self, resource: AbstractResource) -> None:\n        \"\"\"Remove a resource from the resource index.\"\"\"\n        resource_key = self._get_resource_index_key(resource)\n        self._resource_index[resource_key].remove(resource)\n\n    def add_resource(self, path: str, *, name: str | None = None) -> Resource:\n        if path and not path.startswith(\"/\"):\n            raise ValueError(\"path should be started with / or be empty\")\n        # Reuse last added resource if path and name are the same\n        if self._resources:\n            resource = self._resources[-1]\n            if resource.name == name and resource.raw_match(path):\n                return cast(Resource, resource)\n        if not (\"{\" in path or \"}\" in path or ROUTE_RE.search(path)):\n            resource = PlainResource(path, name=name)\n            self.register_resource(resource)\n            return resource\n        resource = DynamicResource(path, name=name)\n        self.register_resource(resource)\n        return resource\n\n    def add_route(\n        self,\n        method: str,\n        path: str,\n        handler: Handler | type[AbstractView],","sourceCodeStart":1087,"sourceCodeEnd":1123,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L1087-L1123","documentation":"add_resource() requires the path argument to be either empty or start with '/'. aiohttp matches paths against request.rel_url.path, which always begins with '/', so a path without a leading slash can never match. Empty string is allowed (used for root-level domain-matched resources). This is the first check in add_resource, before any name validation.","triggerScenarios":"Calling app.router.add_route('GET', 'users', h) (missing leading slash); app.add_get('api/items', h); constructing a path from joined parts without a leading slash: path = 'api/' + resource.","commonSituations":"Building paths by joining fragments and forgetting the leading slash; porting from a framework that doesn't require it (Flask supports 'users' implicitly); config-driven route tables with a missing '/' prefix.","solutions":["Prefix every path with '/': app.router.add_get('/users', h).","Normalize before registering: path = '/' + path.lstrip('/') if path else path.","Lint route tables in config-driven apps to enforce the leading slash."],"exampleFix":"// before\napp.router.add_get('users/{id}', handler)\n// after\napp.router.add_get('/users/{id}', handler)","handlingStrategy":"validation","validationCode":"def safe_path(path: str) -> str:\n    if not path:\n        return path\n    if not path.startswith('/'):\n        path = '/' + path.lstrip('/')\n    return path\n\napp.router.add_get(safe_path(raw_path), h)","typeGuard":"def is_valid_route_path(path: str) -> bool:\n    return isinstance(path, str) and (path == '' or path.startswith('/'))","tryCatchPattern":"try:\n    app.router.add_get(raw_path, h)\nexcept ValueError as e:\n    if 'should be started with /' in str(e):\n        app.router.add_get('/' + raw_path.lstrip('/'), h)\n    else:\n        raise","preventionTips":["Always prefix route paths with '/'.","Normalise joined path fragments with a leading slash before add_route.","Lint route tables in config-driven apps."],"tags":["url-dispatcher","path-routing","validation"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}