Skip to content

fix(doctor): resolve route checks by matching, not flat app.routes scan#66

Merged
tachyon-beep merged 2 commits into
mainfrom
fix/doctor-route-introspection-fastapi-0137
Jun 17, 2026
Merged

fix(doctor): resolve route checks by matching, not flat app.routes scan#66
tachyon-beep merged 2 commits into
mainfrom
fix/doctor-route-introspection-fastapi-0137

Conversation

@tachyon-beep

Copy link
Copy Markdown
Collaborator

Problem

filigree doctor fails (exit 1) with false-positive "missing route" errors on any install that resolves FastAPI ≥ 0.137:

!! Scan results routes: Missing route(s): POST /api/weft/scan-results, POST /api/scan-results, GET /api/files/_schema
!! Entity association routes: Missing route(s): GET/POST/DELETE /api/issue/{issue_id}/entity-associations, GET /api/entity-associations

The routes are served correctly at runtime — only the doctor's static introspection is fooled.

Root cause

FastAPI 0.137 made include_router lazy: child routes are mounted behind a fastapi.routing._IncludedRouter wrapper whose child paths stay unprefixed and compose the /api (and /weft) prefix only at match time. The doctor built a route table by flat-scanning app.routes for .path strings — under 0.137 it sees the wrapper's empty path and concludes every included route is missing.

CI never caught it because uv.lock pinned FastAPI 0.136.3 (which still eager-flattens) while real uv tool installs resolve 0.137.x. Classic "green in CI, broken in the field."

Discovered investigating an "old install" (esper-lite) whose doctor showed these errors. The data store was fully current (schema v28) — the breakage was the opposite of stale: a freshly-resolved newer FastAPI.

Fix

  • _route_supports now resolves routes via Starlette matching (route.matches(scope)) instead of path-string scanning — version-agnostic, method-aware, prefix-composing. Works on both old and new FastAPI.
  • uv.lock moves forward to FastAPI 0.137.1 / Starlette 1.3.1 so CI exercises the version field installs receive. No upper cap added (the matching fix is version-agnostic by design).
  • Closed two pre-existing test-hygiene connection leaks the bump's newer TestClient unmasked via GC timing (tests abandoning a _attempt_startup-opened DB without closing it, tripping the suite's error::ResourceWarning filter). Matches the close-after-startup pattern the sibling _attempt_startup tests already use.
  • Bumps to 3.0.1 so fixed installs are distinguishable from the 3.0.0 builds carrying the false-positive.

Tests

  • New TestRouteSupports (RED-first against the real 0.137 bug): included-router detection, HTTP-method enforcement, absent-route handling.
  • Existing test_run_doctor_emits_real_route_and_auth_checks now genuinely exercises 0.137.
  • Full pipeline green: ruff · ruff format · mypy (112 files) · pytest.

Field rollout

Existing installs need a uv tool install --reinstall of 3.0.1 — not a data migration. Their stores are already current.

🤖 Generated with Claude Code

tachyon-beep and others added 2 commits June 18, 2026 01:24
`filigree doctor`'s dashboard route-registration checks ("Scan results
routes", "Entity association routes") reported registered routes as
missing on any install that resolved FastAPI >= 0.137, failing doctor
(exit 1) even though the routes are served correctly at runtime.

FastAPI 0.137 made `include_router` lazy: child routes are mounted
behind a `_IncludedRouter` wrapper and compose their `/api` (and `/weft`)
prefix only at match time, so the doctor's flat `app.routes` path scan
saw the wrapper's empty path and concluded the routes were missing. CI
never caught it because `uv.lock` pinned FastAPI 0.136.3 while field
installs resolved 0.137.x.

`_route_supports` now resolves routes via Starlette matching
(`route.matches(scope)`) instead of path-string scanning — version-
agnostic, method-aware, prefix-composing. The lock moves forward to
FastAPI 0.137.1 / Starlette 1.3.1 so CI exercises the version field
installs actually receive (no upper cap added).

The lock bump's newer TestClient shifted GC timing and unmasked two
pre-existing test-hygiene leaks: tests that opened a DB via
`_attempt_startup` (process-lifetime on `mcp_server.db`, closed at
shutdown in production) but abandoned the handle without closing it,
so the connection was finalized mid-session and tripped the suite's
`error::ResourceWarning` filter. Closed both, matching the close-after-
startup pattern the other `_attempt_startup` tests already use.

Bumps to 3.0.1 so fixed installs are distinguishable from the 3.0.0
builds carrying the false-positive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Button up the [3.0.1] entry with its compare link, matching the
reference-link convention used for every prior release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tachyon-beep tachyon-beep merged commit 194c559 into main Jun 17, 2026
8 checks passed
@tachyon-beep tachyon-beep deleted the fix/doctor-route-introspection-fastapi-0137 branch June 17, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant