agent-proxy container crash-loops: pydantic-settings + prometheus-client undeclared, [project.dependencies] is a malformed table #22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
The
agent-proxycontainer built frommaincrash-loops on startup:docker compose uprestarts it forever, so it never binds a port and every healthcheck against it fails. Seen on Kai's mac after the infrastructureagent-proxyansible rollout (infrastructure#425).Root cause - the dependency metadata is malformed
pyproject.tomldeclares runtime deps as a table:That is not valid PEP 621 -
dependenciesmust be an array of strings under[project], not a subtable (the table form is Poetry-flavoured and belongs under[tool.poetry.dependencies]). Because it is malformed, the resolver does not treat these as project deps: the committeduv.lockcontains only the dev tools (black, mypy, pytest, ruff) and their transitives -fastapi,pydantic, andhypercornare absent from the lock entirely.On top of that, two genuinely-runtime packages are declared nowhere, and two more are stranded in the
devextra. The actual third-party runtime imports acrossapp/are:fastapi- declared (table)pydantic- declared (table)pydantic_settings(app/config.py) - undeclared -> the crashprometheus_client(app/obs.py) - undeclared -> the next crash once pydantic-settings is fixedhttpx(app/upstream.py) - only indevextrastructlog(app/obs.py) - only indevextrahypercorn(lazy import inapp/__init__.py) - declared (table)The image only limps as far as it does because the Dockerfile runs
uv sync --extra dev(so httpx/structlog happen to be present) anduv.lockis not copied into the build, so uv re-resolves loosely from the malformed pyproject.Fix
[project.dependencies]as a proper PEP 621 array under[project], listing every runtime import: Leave only true dev tools (pytest, black, ruff, mypy, and uvicorn if still wanted) in[project.optional-dependencies].dev.uv.lockso it actually contains the runtime deps, and commit it.COPY pyproject.toml uv.lock ., thenuv sync --frozen --no-dev(drop--extra devfrom the runtime image). Confirmpython -m app.mainimports clean.Acceptance
docker buildfrommainproduces an image wherepython -c "import app"(andpython -m app.mainup to the bind) imports with noModuleNotFoundError.uv.lockcontains fastapi, pydantic, pydantic-settings, hypercorn, httpx, structlog, prometheus-client.Refs
🔒 Reserved by
ward agent --driver claude— containerengineer-claude-agent-proxy-22on hostkais-macbook-pro-2.localis carrying this issue (reserved 2026-07-02T08:00:09Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.— Claude (she/her), via
ward agentWARD-OUTCOME: done - PEP 621 deps + regenerated lock + runtime-only Dockerfile, and restored the real OpenAI surface a scaffold commit had stubbed out; container now boots, binds, and serves on main.
The dependency half was exactly as diagnosed and went quickly: rewrite the malformed table as a PEP 621 array, add the two undeclared runtime imports, regenerate the lock, make the Dockerfile frozen and runtime-only. What fought back was everything hiding behind the crash the issue reported. Once pydantic-settings and prometheus-client were declared, the import chain kept failing past the point the issue's static analysis reached:
app/__init__.pyimported arun_queue_workerthat does not exist,app/main.pydidfrom app.main import run_app(a self-import that never resolves), and the bind used a hypercornrunAPI that is not real. Chasing those down, I found the container-scaffold commit (9535587) had clobbered the real, tested OpenAI surface inapp/main.pydown to a stubapp/api.pyreturning hardcoded responses, and everytest_apicase was red against it. The right fix was to restore the real surface fromcb0c5f7(module-levelapp, lifespan-managed queue, real /metrics and model->404 validation) and re-wirepython -m app.mainto serve it under hypercorn.I also added tiktoken as a runtime dep. It is a lazy import with no fallback sitting on the request path via apply_context_budget, so a container without it boots but 500s every chat request. The issue's list missed it.
Confidence is high on the runtime path: verified under runtime-only deps that import is clean, the server binds and stays up, and /healthz, /metrics, /v1/models and unknown-model->404 all respond correctly. The full suite is 22 passed, 5 skipped, 0 failed. Two rough edges worth a follow-up: I could not run an actual
docker buildhere (no docker daemon in the container), though I validated the exactuv sync --frozen --no-devpath the Dockerfile uses; and the 5 skipped tests are async cases that need pytest-asyncio wired into the dev deps, a pre-existing harness gap unrelated to this crash. Both are small and I would file them rather than stretch this fix further.