Resolves #10908
Authored by Claude Opus 4.7 (Claude Code). Session 7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571.
One-line fix to ai/deploy/docker-compose.test.yml Chroma healthcheck. Same substrate-config-bug pattern as #10904 (Dockerfile prepare-lifecycle): bug masked by local Docker layer caching, surfaced on first clean ubuntu-latest CI build via Lane C #10899 integration row re-fire.
Evidence: L1 (file change minimal + clear; comment explains rationale; Python urllib is stdlib) → L3 in-flight (Lane C #10899's integration row will pass on next rebase + CI run after this lands). No external residuals.
Root Cause
ai/deploy/docker-compose.test.yml:7:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v2/heartbeat"]
interval: 5s
timeout: 5s
retries: 12
The chromadb/chroma:1.5.9 image is debian-slim-based and does not include curl. Healthcheck fails every 5s × 12 retries = 60s, container marked unhealthy, kb-server + mc-server blocked (both have depends_on: chroma: condition: service_healthy). Composing webServer waits → 240s timeout → integration suite fails.
Empirical anchor: Lane C CI run 25506320077 integration job:
15:45:28 — Chroma starts, banner output observed (process is healthy + listening on port 8000).
15:46:28 — dependency failed to start: container neo-integration-test-chroma-1 is unhealthy (exactly 60s, matching 5s × 12 retries).
15:48:39 — Error: Timed out waiting 240000ms from config.webServer.
The Chroma server itself is running and listening — the healthcheck just can't probe it.
Fix
+ # The chromadb/chroma image is debian-slim-based and does NOT include curl.
+ # Python is the runtime (Chroma is a Python app), so use urllib for the probe.
+ # Same target endpoint, fallback-safe across all chromadb image variants. (#10908)
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8000/api/v2/heartbeat"]
+ test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/api/v2/heartbeat',timeout=2).read() else 1)"]
interval: 5s
timeout: 5s
retries: 12Python urllib.request.urlopen is stdlib — no external dependency, guaranteed in the chromadb image (Python is the Chroma runtime). 2s timeout per probe well under the 5s envelope. Returns exit 0 on any 2xx + non-empty body, exit 1 on any failure (HTTP error, connection refused, timeout, malformed response).
Avoided Traps
- Rejected: install curl in a custom Chroma Dockerfile. Adds image-build complexity for a healthcheck-only utility.
- Rejected: drop the healthcheck + use
service_started instead of service_healthy. Race condition: kb/mc may start before Chroma is actually ready and fail with connection-refused on first MCP request.
- Rejected: switch to
wget. Same problem as curl — not guaranteed in chromadb image.
- Rejected: bash
/dev/tcp probe. Requires bash (not just sh); chromadb image basis isn't guaranteed to have bash. Less portable than Python.
Sibling Lane Impact
This fix unblocks the integration-test substrate (3rd substrate-config bug surfaced by Lane C CI; #10902 + #10908 form a "first clean Docker build catches the masking" pattern):
- Lane C #10899 — own PR; CI gating depends on this. Will rebase + retrigger CI after this merges.
- Lane B #10898 — Gemini's sustained-liveness PR; merged on
dev already (via @tobiu). Future PRs from Lane B's substrate gain L2/L3 evidence path post-merge.
- Lane A #10901 — GPT's tenant-isolation; merged. Same.
- Bucket B/C/D PRs in flight (#10903 distribution) — all integration-touching tests gain CI-runnable evidence post-merge.
Test Evidence
L1 verification:
git diff minimal: 4 lines added (3 comment + 1 substantive), 1 line removed.
- Python expression validated mentally —
urllib.request.urlopen is stdlib, semantics straightforward.
- Local Docker not available on my host (verified earlier), so empirical L3 deferred to Lane C CI re-run post-merge.
L3 verification (deferred to convergence):
- Once this PR merges to
dev, Lane C #10899 Tests / integration matrix row passes on next rebase + run.
Post-Merge Validation
Cross-family review request
This touches @neo-gemini-pro's #10880 substrate (same authorship-respect pattern as #10904). Routing to @neo-gemini-pro as primary reviewer per Authorship-respect heuristic + cross-family pairing.
Origin Session ID: 7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571
Resolves #10908
Authored by Claude Opus 4.7 (Claude Code). Session 7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571.
One-line fix to
ai/deploy/docker-compose.test.ymlChroma healthcheck. Same substrate-config-bug pattern as #10904 (Dockerfile prepare-lifecycle): bug masked by local Docker layer caching, surfaced on first clean ubuntu-latest CI build via Lane C #10899 integration row re-fire.Evidence: L1 (file change minimal + clear; comment explains rationale; Python
urllibis stdlib) → L3 in-flight (Lane C #10899's integration row will pass on next rebase + CI run after this lands). No external residuals.Root Cause
ai/deploy/docker-compose.test.yml:7:healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/api/v2/heartbeat"] interval: 5s timeout: 5s retries: 12The
chromadb/chroma:1.5.9image is debian-slim-based and does not includecurl. Healthcheck fails every 5s × 12 retries = 60s, container marked unhealthy,kb-server+mc-serverblocked (both havedepends_on: chroma: condition: service_healthy). Composing webServer waits → 240s timeout → integration suite fails.Empirical anchor: Lane C CI run 25506320077 integration job:
15:45:28— Chroma starts, banner output observed (process is healthy + listening on port 8000).15:46:28—dependency failed to start: container neo-integration-test-chroma-1 is unhealthy(exactly 60s, matching5s × 12 retries).15:48:39—Error: Timed out waiting 240000ms from config.webServer.The Chroma server itself is running and listening — the healthcheck just can't probe it.
Fix
+ # The chromadb/chroma image is debian-slim-based and does NOT include curl. + # Python is the runtime (Chroma is a Python app), so use urllib for the probe. + # Same target endpoint, fallback-safe across all chromadb image variants. (#10908) healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/api/v2/heartbeat"] + test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/api/v2/heartbeat',timeout=2).read() else 1)"] interval: 5s timeout: 5s retries: 12Python
urllib.request.urlopenis stdlib — no external dependency, guaranteed in the chromadb image (Python is the Chroma runtime). 2s timeout per probe well under the 5s envelope. Returns exit 0 on any 2xx + non-empty body, exit 1 on any failure (HTTP error, connection refused, timeout, malformed response).Avoided Traps
service_startedinstead ofservice_healthy. Race condition: kb/mc may start before Chroma is actually ready and fail with connection-refused on first MCP request.wget. Same problem as curl — not guaranteed in chromadb image./dev/tcpprobe. Requires bash (not just sh); chromadb image basis isn't guaranteed to have bash. Less portable than Python.Sibling Lane Impact
This fix unblocks the integration-test substrate (3rd substrate-config bug surfaced by Lane C CI; #10902 + #10908 form a "first clean Docker build catches the masking" pattern):
devalready (via @tobiu). Future PRs from Lane B's substrate gain L2/L3 evidence path post-merge.Test Evidence
L1 verification:
git diffminimal: 4 lines added (3 comment + 1 substantive), 1 line removed.urllib.request.urlopenis stdlib, semantics straightforward.L3 verification (deferred to convergence):
dev, Lane C #10899Tests / integrationmatrix row passes on next rebase + run.Post-Merge Validation
integrationmatrix row passes (Chroma container reachesservice_healthy, kb-server + mc-server start,npm run test-integrationruns assertions).docker compose -f ai/deploy/docker-compose.test.yml up --buildreaches "ready" state on a clean checkout.Cross-family review request
This touches @neo-gemini-pro's #10880 substrate (same authorship-respect pattern as #10904). Routing to @neo-gemini-pro as primary reviewer per Authorship-respect heuristic + cross-family pairing.
Origin Session ID:
7e897a0b-33ce-4d6c-b1a9-a1ff93e4e571