Repository navigation
fix(agent-runtime): agent server boots under FastAPI 0.142 auto-telemetry; pin its deps (#3106) - #3107
Conversation
…etry; pin its deps (#3106) startup.sh upgraded the agent-server dependencies UNPINNED on every boot, so each container start took PyPI's latest. FastAPI 0.142 auto-configures OpenTelemetry from the standard OTEL_* env at lifespan startup and refuses the grpc protocol Trinity injects for Claude Code: every OTEL-enabled agent's server exited on its next boot while the container stayed Up. Three layers: - agent server opts out of FastAPI auto-telemetry (the OTEL_* env is Claude Code's), passed only when the installed FastAPI has the parameter; - agent-server deps exact-pinned in agent-server-requirements.txt, used by the image build and by the boot-time repair (no unpinned --upgrade); - backend injects OTEL_SDK_DISABLED=true beside the OTEL env at create, recovery and system-agent create, rescuing agents on older images on recreate. Claude Code does not read it, so its metrics keep flowing. Verified against FastAPI 0.142.1 with the grpc env: lifespan startup fails without the opt-out and completes with it (and with the env flag alone); a freshly built image boots the server on 0.141.1, still boots when a template installs 0.142.1, and the boot repair restores the pin. Fixes #3106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eleases unreviewed (#3106) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/review ReportBranch: Execution coverage
Fix mutations (8 of 8 red). Each change reverted one at a time on a scratch copy:
Manual verification (from the PR, re-read here):
Critical FindingsNone. Informational Findings[I1] Incomplete fix (4.14):
[I2] Behaviour change: the exact pins now downgrade packages a template installed (Confidence: 8/10)
[I3]
[I4] The regression test the issue asks for runs only by hand (Confidence: 6/10)
[I5] Nothing will bump the new pins (Confidence: 8/10)
Clean Categories
Summary
|
…endabot for the pins (#3106) - Recreate replays the old Config.Env, so existing agents never gained OTEL_SDK_DISABLED on restart / drift self-heal / rebuild. The recreate path now sets it (setdefault) wherever Claude Code's OTEL env is present. Behavioural tests through the real lifecycle seam. - Split the pins: the image build installs exact pins (agent-image-requirements.txt); the per-boot repair installs floors for the server's own packages only (agent-server-requirements.txt), so it never downgrades a newer version a template brought and no longer touches rich/cryptography. Floors are tested to sit at or below the pins. - Dependabot pip entry for /docker/base-image, the pins' bump channel. Verified on a rebuilt image with the grpc OTEL env: fresh boot serves on 0.141.1; a template's 0.142.1 is kept and serves; a template's 0.115.6 is raised past the floor and serves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/review Report — re-run after fixes (
|
| # | Finding | Status |
|---|---|---|
| I1 | OTEL_SDK_DISABLED never reached existing agents on recreate |
✅ Fixed. lifecycle.py recreate path: if 'OTEL_EXPORTER_OTLP_PROTOCOL' in env_vars: env_vars.setdefault('OTEL_SDK_DISABLED', 'true'). Three behavioural tests through the real recreate_container_with_updated_config seam: an OTEL agent gains it; a non-OTEL agent is untouched; an explicit operator value wins. The PR body overclaim is corrected. |
| I2 | Exact pins downgraded template packages at boot | ✅ Fixed. Split into agent-image-requirements.txt (exact, build only) and agent-server-requirements.txt (floors, boot only, server packages only; rich/cryptography removed from the boot set). Floors are tested to sit at or below the pins. |
| I3 | OTEL_SDK_DISABLED disables any OTel SDK in template code |
ℹ️ Accepted, and now stated in the PR's Side effects. |
| I4 | No CI boot of the server under FastAPI ≥ 0.142 | ℹ️ Follow-up, now listed in the PR. |
| I5 | No bump channel for the pins | ✅ Fixed. Dependabot pip entry for /docker/base-image; the config validation check passed on this PR. |
Execution coverage
| changed symbol / file | executed by | live consumer | verdict |
|---|---|---|---|
fastapi_app_options |
3 tests (both constructor shapes + installed FastAPI) | agent_server/main.py:60 |
✅ executed |
recreate-path setdefault |
3 behavioural tests via recreate_container_with_updated_config |
every restart / drift self-heal / rebuild recreate | ✅ executed |
OTEL_SDK_DISABLED at create / recovery / system agent |
AST scan of every OTEL_EXPORTER_OTLP_PROTOCOL writer |
FastAPI in the agent container | 🛡 guard + manual verification |
agent-image-requirements.txt / agent-server-requirements.txt |
pin, floor, coverage and floor ≤ pin tests | Dockerfile build line, startup.sh:616 |
🛡 guard |
Dockerfile / startup.sh pip lines |
source-text tests | image build / container boot | 🛡 guard + real boots (below) |
.github/dependabot.yml |
GitHub's config validation check | Dependabot | 🛡 guard |
Fix mutations: 14 of 14 red, each reverted on a scratch copy and the tree restored:
| Area | Mutations |
|---|---|
| Opt-out | dropped from main.py; helper returns {} |
| Boot repair | back to unpinned --upgrade; switched to the exact pins (would downgrade) |
| Image build | back to unpinned |
| Env flag at create sites | DB recovery loses it; system agent loses it |
| Recreate path | loses the flag; overwrites an operator value; sets it on non-OTEL agents |
| Pin files | image pin moved to 0.142.1; a loose image pin; a floor above its pin; boot set grows past the server packages |
Real boots from an image rebuilt at this commit, with ENABLE_AGENT_UI=true and OTEL_EXPORTER_OTLP_PROTOCOL=grpc:
| Case | FastAPI after restart | Health |
|---|---|---|
| Fresh container | 0.141.1 (repair did nothing) | 200 |
| Template installed 0.142.1 | 0.142.1 (not downgraded) | 200 |
| Template installed 0.115.6 | raised to 0.142.1 | 200 |
Neighbouring suites (recreate / env / base-image / startup): 855 passed, 1 skipped.
Critical Findings
None.
Informational Findings
[I6] A below-floor boot repair installs the latest release, not the image pin (Confidence: 7/10)
File: docker/base-image/startup.sh:616
- Issue: when a template pushes a server package below its floor, the repair takes PyPI's latest (observed: 0.115.6 → 0.142.1), so a new upstream release can still enter a container on this path.
- Why it's acceptable here: it's much narrower than before (only below-floor, only server packages), and a newer FastAPI is safe because of the opt-out.
- Suggestion (optional): pass the exact pins as an upper bound on this path only, e.g.
-cwith a generated<=constraints file, if you want no unvetted release on any path. Stated in the PR's Side effects.
[I7] Dependabot's handling of the floor file is unverified (Confidence: 5/10, appendix-level)
- Dependabot
pipreads*requirements*.txtin the listed directory, so both files are in scope. For the floor file it may widen or leave the>=ranges rather than bump them. Harmless either way, because the floors only need to stay at or below the exact pins, which the tests enforce.
Clean Categories
- SQL / data safety, auth boundaries, frontend: not touched.
- Concurrency: the recreate mutation is a local dict edit before the container handoff; no shared state.
- Credential exposure: the only new env value is the literal
true; gitleaks passed. - Enterprise disclosure: none.
- Enum completeness: all four agent-env writers covered;
template_service._PLATFORM_INJECTED_PREFIXESalready includesOTEL_. - Boot fragility:
startup.shhas noset -e, so the repair stays non-fatal. With floors satisfied, the boot pip call needs no network. - Doc staleness: none; the learnings fragment is present.
CI at posting time
Passed: prod-image-smoke, backend boots without enterprise submodule, schema-parity, gitleaks, the Dependabot config check, guard, journey-impact, and the compose checks. Pending: pytest (head), journey-smoke, verify-non-root, CodeQL.
Summary
- Critical: 0.
- Informational: 2 new (I6 optional, I7 appendix). All actionable prior findings are fixed and mutation-proven.
- Verdict: ready to merge once the pending CI checks pass.
dev moved past 0082 (0083 to 0085 landed), so 0083_seat_ask_class_state forked the Alembic graph into two heads, which alembic-head-watch flagged. It is now 0087_seat_ask_class_state on 0085_ent720_email_identity. 0087 rather than 0086 because #3021's pull_sync takes 0086; whichever of the two merges second re-parents onto the other. The SQLite list keeps both sides, with dev's entries first. The merge also brings #3107, the agent-server boot fix whose absence failed journey-smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev moved past 0082 (0083 to 0085 landed), so 0083_pull_sync forked the Alembic graph into two heads, which alembic-head-watch flagged. It is now 0086_pull_sync on 0085_ent720_email_identity, and the SQLite list keeps both sides, with dev's entries first and pull_sync after. The merge also brings #3107, the agent-server boot fix whose absence failed journey-smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Every OTEL-enabled agent's server stopped starting on its next boot.
startup.shupgraded the agent-server dependencies unpinned on every container start, so it pulled FastAPI 0.142. That release auto-configures OpenTelemetry from the standardOTEL_*env and refuses thegrpcprotocol Trinity injects for Claude Code. The container stayedUpwhile nothing listened on:8000.Three layers:
agent_server/fastapi_options.pypassestelemetry={"auto_configure": False}, but only when the installed FastAPI has that parameter, so a template that pins an older FastAPI still boots. TheOTEL_*env belongs to Claude Code, not the server. With this layer, any FastAPI version is safe for the server.agent-image-requirements.txtholds exact pins, used by the image build, so rebuilds are reproducible (the Default model claude-opus-5-5 fails on cached Claude Code 2.1.278, and the rejection is misclassified as auth (seat rotation + API-key fallback) #3012 lesson).agent-server-requirements.txtholds floors for the server's own packages, used bystartup.sh's per-boot repair. It installs what is missing or older than the floor and leaves a newer version alone. The agent server and template code share~/.local, so a template's own upgrades survive a restart, and the repair no longer touchesrich/cryptography./docker/base-image, the pins' bump channel.OTEL_SDK_DISABLED=trueis set wherever Claude Code's OTEL env is:crud.py), DB recovery (lifecycle.py) and system-agent create;recreate_container_with_updated_config, which replays the oldConfig.Env) viasetdefault, so an operator's explicit value wins. This covers existing agents on restart, drift self-heal or a rebuild pass before the base image is rebuilt.Verification
Reproduced first: FastAPI 0.142.1 with
OTEL_EXPORTER_OTLP_PROTOCOL=grpcexits withFastAPI automatic telemetry requires the OTLP http/protobuf protocol.Lifespan startup on 0.142.1 with the grpc env: fails without the opt-out; completes with it; also completes with
OTEL_SDK_DISABLED=truealone.Rebuilt image, real boots with
ENABLE_AGENT_UI=trueand the grpc env:Tests:
tests/unit/test_3106_agent_server_fastapi_telemetry.pyhas 14 tests, including behavioural recreate-path tests through the real lifecycle seam. The neighbouring suites (recreate / env / base-image / startup): 855 passed, 1 skipped.Side effects
OTEL_SDK_DISABLEDswitches off any OpenTelemetry SDK in the container. Nothing that ships uses one (no OTel packages in the image), but a customer template exporting its own traces would stop.Operator note
After deploying, rebuild the base image (
./scripts/deploy/build-base-image.sh). Until then, existing agents are protected by layer 3 on their next recreate.Follow-up
A CI step that boots
agent_server.main:appinside the built image with the grpc env. The test venv pins FastAPI 0.115, so unit tests cannot run the 0.142 boot.Fixes #3106
🤖 Generated with Claude Code