{"id":"15fb1e350e1b4caf","repo":"aio-libs/aiohttp","slug":"base-url-must-have-a-trailing","errorCode":null,"errorMessage":"base_url must have a trailing '/'","messagePattern":"base_url must have a trailing '/'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/client.py","lineNumber":337,"sourceCode":"        max_line_size: int = 8190,\n        max_field_size: int = 8190,\n        max_headers: int = 128,\n        fallback_charset_resolver: _CharsetResolver = lambda r, b: \"utf-8\",\n        middlewares: Sequence[ClientMiddlewareType] = (),\n        ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,\n    ) -> None:\n        # We initialise _connector to None immediately, as it's referenced in __del__()\n        # and could cause issues if an exception occurs during initialisation.\n        self._connector: BaseConnector | None = None\n        if base_url is None or isinstance(base_url, URL):\n            self._base_url: URL | None = base_url\n            self._base_url_origin = None if base_url is None else base_url.origin()\n        else:\n            self._base_url = URL(base_url)\n            self._base_url_origin = self._base_url.origin()\n            assert self._base_url.absolute, \"Only absolute URLs are supported\"\n        if self._base_url is not None and not self._base_url.path.endswith(\"/\"):\n            raise ValueError(\"base_url must have a trailing '/'\")\n\n        if not isinstance(ssl, SSL_ALLOWED_TYPES):\n            raise TypeError(\n                \"ssl should be SSLContext, Fingerprint, or bool, \"\n                f\"got {ssl!r} instead.\"\n            )\n\n        loop = asyncio.get_running_loop()\n\n        if timeout is sentinel or timeout is None:\n            timeout = ClientTimeout()\n        if not isinstance(timeout, ClientTimeout):\n            raise ValueError(\n                f\"timeout parameter cannot be of {type(timeout)} type, \"\n                \"please use 'timeout=ClientTimeout(...)'\",\n            )\n        self._timeout = timeout\n","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client.py#L319-L355","documentation":"Raised in ClientSession.__init__ (client.py:337) as a ValueError when a base_url is supplied whose path does not end with '/'. aiohttp concatenates base_url with per-request relative URLs and relies on RFC 3986 path resolution, which only behaves predictably when the base path ends in '/'. This is a fail-fast configuration check at construction time.","triggerScenarios":"ClientSession(base_url='https://api.example.com/v1') (no trailing slash) — or any base_url string/URL whose .path does not terminate with '/'.","commonSituations":"Copying an API base URL straight from a provider's docs (which usually omit the trailing slash), or constructing base_url dynamically without normalizing.","solutions":["Add a trailing '/' to base_url: 'https://api.example.com/v1/'.","Normalize dynamically-built base URLs with yarl: URL(s).with_path(s.path.rstrip('/') + '/').","If you need the origin only, pass base_url without a path or leave it None."],"exampleFix":"# before\nsession = aiohttp.ClientSession(base_url='https://api.example.com/v1')\n\n# after\nsession = aiohttp.ClientSession(base_url='https://api.example.com/v1/')","handlingStrategy":"validation","validationCode":"from yarl import URL\nbase = URL('https://api.example.com/v1')\nif not base.path.endswith('/'):\n    base = base.with_path(base.path + '/')\nsession = aiohttp.ClientSession(base_url=base)","typeGuard":"def valid_base_url(u: str) -> bool:\n    return u.endswith('/') or URL(u).path.endswith('/')","tryCatchPattern":"try:\n    session = aiohttp.ClientSession(base_url=base)\nexcept ValueError:\n    base = base.rstrip('/') + '/'\n    session = aiohttp.ClientSession(base_url=base)","preventionTips":["Always end base_url with '/'","Normalize dynamically-built base URLs before passing them"],"tags":["client","configuration","validation","url","constructor"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}