fix(starlette): Stop duplicating scope["root_path"] in URLs#6579
7 issues
Medium
test_request_url fails on Django < 5.1 due to incorrect root_path setup - `sentry_sdk/integrations/django/asgi.py:103`
The new test_request_url test sets scope["path"] = "/root/nomessage" with scope["root_path"] = "/root" (path already includes root_path), but only skips for Django < 3.0. On Django < 5.1, path_includes_root_path=False causes the URL to be constructed as root_path + path = "/root/root/nomessage", which will fail the assertion == "/root/nomessage". Consider adding @pytest.mark.skipif(django.VERSION < (5, 1), ...) or adding a separate test case for the pre-5.1 path format.
Also found at:
tests/integrations/django/asgi/test_asgi.py:1093-1101
Sentry monitoring silently disabled when Quart package version cannot be determined - `sentry_sdk/integrations/quart.py:95-100`
When package_version("quart") returns None (e.g., Quart installed from source without version metadata), the new early-return guard silently skips the SentryAsgiMiddleware entirely, disabling all error capture and tracing for the application. Consider defaulting to path_includes_root_path=False (the pre-spec behavior) instead of bypassing the middleware.
test_request_url doesn't exercise the root_path duplication bug for Starlette - `tests/integrations/starlette/test_starlette.py:1486-1525`
The test uses TestClient with the default root_path="", so the outer ASGI scope always has an empty root_path. The Sentry middleware (guarded by _asgi_middleware_applied) only processes this outer scope, meaning root_path + path and path produce the same URL regardless of whether path_includes_root_path is True or False. The bug (duplication when root_path is non-empty and Starlette >= 0.33 includes it in path) is never triggered. Compare to the Django ASGI test, which correctly sets comm.scope["root_path"] = "/root" with path = "/root/nomessage" to verify no duplication. The Starlette test should use TestClient(starlette_app, root_path="/root") with a request to /nomessage and assert the URL is http://testserver/root/nomessage, not http://testserver/root/root/nomessage.
Sentry monitoring silently disabled when Quart version is undetectable - `sentry_sdk/integrations/quart.py:97-99`
When package_version("quart") returns None (e.g. editable installs or unusual packaging), the or version is None guard causes the patched function to return the raw old_app without any Sentry middleware, silently dropping all transaction tracking and error capturing. The safe fallback is to use path_includes_root_path=False (old behavior) rather than skipping the middleware entirely.
test_request_url fails on Django < 5.1 due to incorrect skipif condition - `tests/integrations/django/asgi/test_asgi.py:1052-1101`
The test passes /root/nomessage as scope["path"] with scope["root_path"] = "/root", but for Django < 5.1 the integration uses path_includes_root_path=False, causing _get_url() to return "/root" + "/root/nomessage" = "/root/root/nomessage", which doesn't match the assertion == "/root/nomessage". The test should be guarded with skipif(django.VERSION < (5, 1)) or use a different path for older Django.
Low
New default `path_includes_root_path=True` changes URL behavior for direct `SentryAsgiMiddleware` users on non-spec-compliant ASGI apps - `sentry_sdk/integrations/asgi.py:120`
The previous behavior always prepended scope["root_path"] to scope["path"] when building URLs (equivalent to path_includes_root_path=False). The new default True assumes path already includes root_path, which is the ASGI-spec-compliant behavior and fixes a duplication bug for compliant apps. However, users who wrap a non-spec-compliant ASGI app (e.g. old Starlette, Starlite, LiteStar, old Quart) directly with SentryAsgiMiddleware — rather than via the bundled integrations — will now silently get URLs that omit the root_path prefix when root_path is non-empty. The bundled integrations pass path_includes_root_path=False for these frameworks, but direct middleware users have no automatic protection and the new parameter is not documented in the __init__ docstring. This is a narrow backwards-compatibility regression (only manifests with a non-empty root_path on a non-compliant app wrapped directly).
Channels < 3.0.0 path missing `path_includes_root_path=False`, causing root_path to be dropped from URLs - `sentry_sdk/integrations/asgi.py:152-157`
In patch_channels_asgi_handler_impl, the SentryAsgiMiddleware created for the channels < 3.0.0 branch does not pass path_includes_root_path, so it inherits the new default of True. For Django < 5.1 (always the case with channels < 3.0.0), scope["path"] does not include scope["root_path"], so path_includes_root_path should be False — matching what patch_django_asgi_handler_impl explicitly does for the same Django version condition. When root_path is non-empty, the URL recorded in Sentry events will be missing the root path prefix.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| security-review | 0 | 12.1s | $0.24 |
| code-review | 4 | 6m 29s | $3.43 |
| find-bugs | 3 | 17m 25s | $5.91 |
| skill-scanner | 0 | 12m 20s | $0.15 |
⏱ 36m 26s · 6.2M in / 267.8k out · $9.73