Skip to content

fix: raise ConfigurationError from bootstrap() after teardown() - #347

Merged
lesnik512 merged 2 commits into
mainfrom
raise-bootstrap-after-teardown
Oct 28, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
raise-bootstrap-after-teardown

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Fixes #326.

teardown() reset is_bootstrapped, so a later bootstrap() ran every instrument's bootstrap() again. Each one appended to the same application or AppConfig it had already changed, and nothing removed what it had added the first time:

  • Litestar raised ImproperlyConfiguredException on the duplicate /health route (or /metrics with health checks off).
  • FastAPI, FastStream and FastMCP didn't raise. They registered the health and metrics routes a second time and added the Prometheus middleware twice, so every request, message or tool call was counted twice.
  • Any bootstrapper with OpenTelemetry kept the shut-down TracerProvider, because a provider can only be set once per process.

The issue offered two options: make re-bootstrap work, or refuse it. This PR refuses it. Once teardown() has run, bootstrap() raises ConfigurationError and tells the user to construct a new bootstrapper, with a new application if the bootstrapper takes one. This matches the one-bootstrapper-per-application rule that _attach_teardown_once already enforces. Supporting it would have meant making every instrument idempotent across five frameworks, and OpenTelemetry would still be stuck with its first provider.

The flag is set only when teardown() actually tears something down. A teardown() before any bootstrap() still returns early and doesn't block a later bootstrap(). Repeated bootstrap() calls before teardown still return the cached application (#325).

Tests, written first, all failing on main:

  • test_<framework>_bootstrap_after_teardown_raises for Free, FastAPI, FastStream, FastMCP and Litestar. Each one bootstraps with the default config, tears down, and expects ConfigurationError from the second bootstrap(). Four failed with "DID NOT RAISE"; Litestar failed with its duplicate-route error.
  • test_litestar_bootstrap_after_teardown_builds_a_new_application from fix: return the same application from a repeated bootstrap() #325 asserted the opposite. It ran with health checks and Prometheus off to avoid this bug. It is replaced by the Litestar test above.

CONTEXT.md: the Bootstrapper entry now says a torn-down bootstrapper can't bootstrap again. The Free entry said Free has "no double-bootstrap guard", which now reads wrong, so it says "no guard against a second bootstrapper" instead.

Checked locally: lint and ty are clean, all 397 tests pass, and coverage is 100%. I didn't run the floors steps locally; the change touches no dependency APIs.

Behaviour change: code that calls bootstrap() again on the same bootstrapper after teardown() now gets ConfigurationError. On FastAPI, FastStream, FastMCP and Free it used to work, but on the frameworks it duplicated routes and metrics. Construct a new bootstrapper instead. No config changes.

teardown() reset is_bootstrapped, so a later bootstrap() ran every instrument again against the application it had already changed. Litestar raised on the duplicate /health route; FastAPI, FastStream and FastMCP silently registered health and metrics routes and Prometheus middleware twice. A torn-down bootstrapper now refuses to bootstrap again.

Fixes #326.
@lesnik512
lesnik512 merged commit 4f49688 into main Oct 28, 2026
35 checks passed
@lesnik512
lesnik512 deleted the raise-bootstrap-after-teardown branch October 28, 2026 13:28
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.

bootstrap() after teardown() registers routes and middleware twice

1 participant