Skip to content

fix(otel): harden partial-install detection + lazy-import (0.8.4)#35

Merged
lesnik512 merged 6 commits into
mainfrom
fix/otel-partial-install
Jun 8, 2026
Merged

fix(otel): harden partial-install detection + lazy-import (0.8.4)#35
lesnik512 merged 6 commits into
mainfrom
fix/otel-partial-install

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

Closes 2 of the remaining audit findings — the OpenTelemetry partial-install hazard from chunk 2. See planning/specs/2026-06-08-otel-partial-install-design.md for the design and planning/releases/0.8.4.md for the user-facing release notes.

The hazard

  • find_spec("opentelemetry") returns truthy because opentelemetry/ is a PEP 420 namespace package — created by any opentelemetry-instrumentation-* install.
  • is_otel_installed becomes True even when opentelemetry-api is absent.
  • The lazy from opentelemetry import trace in _emit_event was unguarded — raised ImportError mid-request, crashing the middleware.

The fixes (paired per the audit's recommendation)

  • import_checker.is_otel_installed probes importlib.metadata.distribution("opentelemetry-api") — closes the false-positive at detection time without the sys.modules side-effect that find_spec("opentelemetry.trace") would have introduced.
  • _emit_event wraps the lazy import in try/except ImportError — closes the runtime crash, degrades to log-only emission.

Audit findings closed

Severity File:line Closed by
Low _internal/import_checker.py:8 importlib.metadata.distribution probe
Low _internal/observability.py:40 try/except ImportError wrap

Spec deviation

The spec called for find_spec("opentelemetry.trace") as the new probe. During implementation, this was found to break the existing test_importing_httpware_does_not_import_opentelemetry isolation test — CPython resolves submodule probes by importing the parent namespace package, populating sys.modules as a side-effect. importlib.metadata.distribution("opentelemetry-api") probes the package registry directly with no sys.modules side effects, and is arguably a tighter match for the spec's intent ("require the api package").

Test plan

  • New source-pinning test guards the chosen probe (distribution("opentelemetry-api") must appear in import_checker.py).
  • New runtime test simulates the partial-install crash via sys.modules['opentelemetry'] = _BrokenOpenTelemetry() and verifies _emit_event returns without raising while still emitting the structured log record.
  • just lint-ci + full retry + bulkhead + observability + isolation suites green after each commit.

Release

Tag 0.8.4 from the merge SHA after this PR lands.

🤖 Generated with Claude Code

lesnik512 and others added 5 commits June 8, 2026 12:16
Pairs find_spec('opentelemetry.trace') with try/except ImportError around
the lazy `from opentelemetry import trace`. Closes the partial-install
hole where opentelemetry/ namespace directory exists but api package is
missing or broken — currently crashes a live request mid-cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each fix is TDD: failing test first, then production change. T1 pins the
find_spec target via source check (the live probe returns non-None either
way under --all-extras). T2 simulates the crash with sys.modules[
'opentelemetry'] = _BrokenOpenTelemetry() that raises ImportError on
attribute access. T3 = release notes + PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`opentelemetry` is a PEP 420 native namespace package — any
`opentelemetry-instrumentation-*` package creates the directory even when
`opentelemetry-api` is absent. `find_spec("opentelemetry")` then returns
non-None and `is_otel_installed` becomes True; the lazy
`from opentelemetry import trace` in `_emit_event` subsequently raises
ImportError mid-request.

`find_spec("opentelemetry.trace")` would fix the false-positive but causes
CPython to load the opentelemetry namespace package into sys.modules as a
side-effect of resolving the submodule, breaking the transitive-import
isolation guarantee enforced by test_optional_extras_isolation.py.

Use `importlib.metadata.distribution("opentelemetry-api")` instead — it
probes the package registry directly with no sys.modules side-effects, and
raises PackageNotFoundError when the api package is absent. The check is
now cheap and correct under every install permutation we can construct.

The new test source-checks the probe target so a future revert to
find_spec trips the regression guard, even though live distribution() calls
succeed either way under --all-extras.

Closes audit Low finding (import_checker.py:8) from
planning/audit/2026-06-07-deep-audit.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_emit_event gated the OTel path on `if import_checker.is_otel_installed` but
ran `from opentelemetry import trace` unguarded. If is_otel_installed was
True yet the import failed (PEP 420 namespace false-positive in 0.8.3 and
earlier; or any future partial install / broken api package), the
ImportError escaped _emit_event and crashed the middleware calling it
(AsyncRetry, Retry, AsyncBulkhead, Bulkhead) mid-request.

Wrap the lazy import in `try/except ImportError`. On failure, return — the
structured log record on the line above has already fired, so emission
degrades to log-only.

Catch ImportError specifically, not bare Exception: misconfigured-tracer
crashes (RuntimeError, AttributeError) should still surface; only the
install-gate-is-wrong case is in scope.

_emit_event's docstring grows one sentence describing the soft-fallback.

Closes audit Low finding (observability.py:40) from
planning/audit/2026-06-07-deep-audit.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two paired defensive fixes (importlib.metadata.distribution probe +
try/except ImportError wrap) close the chunk-2 partial-install audit
findings. No API change; the only observable behavior change is "no
longer crashes" on partial installs.

Note: the spec called for find_spec("opentelemetry.trace") as the new
probe; release notes reflect what actually shipped (distribution probe,
chosen to avoid a sys.modules side-effect that breaks the existing
isolation test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 self-assigned this Jun 8, 2026
CI failed across all Python versions because the new try/except branch in
import_checker.py (`except PackageNotFoundError: is_otel_installed = False`)
is unreachable under `--all-extras` — opentelemetry-api IS installed, so
the False branch never runs at module load time. Same gap for the new
test's `else` cleanup branch.

Two fixes that satisfy the project's 100% coverage rule:

- Extract the probe into `_is_distribution_installed(name)` helper. Two
  new unit tests cover both branches: monkeypatch distribution() to raise
  PackageNotFoundError (False path); call against opentelemetry-api which
  IS installed (True path).
- Rewrite the existing partial-install test to use `monkeypatch.setitem`
  instead of manual save/restore — no else branch needed.

The source-pinning assertion (regression guard against revert to find_spec
on the namespace) split into two simple `in` checks to satisfy PT018.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit b95f357 into main Jun 8, 2026
5 checks passed
@lesnik512 lesnik512 deleted the fix/otel-partial-install branch June 8, 2026 11:19
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