build: bump python from cea0e60 to a7fb1e6 in /.devcontainer - #423
Conversation
|
ZETETIC-REVIEW: REQUEST_CHANGES Stakes: Low (single-line digest bump, Digest verification (point 1)Resolved Blocking — comment block above the changed line is wrong (§8 source discipline)Not introduced by this diff, but sitting directly above the line it modifies (
The actual pinned tag is Blocking — branch is stale against
|
189ba8f to
565e862
Compare
|
@dependabot recreate |
Bumps python from `cea0e60` to `a7fb1e6`. --- updated-dependencies: - dependency-name: python dependency-version: 3.14-slim dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
565e862 to
3bbcc56
Compare
…container gate docker_smoke.sh drove the container with `printf '%s' "$REQUESTS" | docker run --rm -i ...`: printf closes its end of the pipe (the container's stdin) the instant the batch is written, before any response has been read. That is the exact anti-pattern PR #331 fixed in scripts/mcp_host_client.py for a local subprocess: closing stdin is the MCP shutdown signal (2025-06-18 SS Lifecycle -> Shutdown -> stdio), not an end-of-input marker, and mcp 2.0.0's _handle_request drops an in-flight response write rather than deliver it once EOF fires the cancel scope. Verified against jsonrpc_dispatcher.py that this cancellation path is method-agnostic, so tools/list (id=3) is exactly as vulnerable as any other request — matching this gate's observed signature ("no valid tools/list response (id=3)", empty stderr, no JSON-RPC error frame) and its history of intermittent failures on unrelated PRs and on main itself. Fix: scripts/docker_smoke_client.py drives the container the same way mcp_host_client.py drives a local server — keep stdin open until every expected response id has arrived (mcp_host_client.drain_exchange, a new generic primitive extracted from _exchange, behavior-preserving), close it only then. docker_smoke.sh now builds the image and delegates the run+exchange+assert sequence to this module; the watchdog still docker-kills the container via --cidfile on the same 60s deadline (fires at most once, never retries). Deterministic reproduction (no clock, no docker, no retries): - tests_py/infrastructure/test_stdio_eof_drain.py adds TestDockerSmokeToolsListLostBeforeDrain, using docker_smoke's own id=3/tools/list request against the real mcp 2.0.0 SDK: the write-then-close-before-drain shape drops the response (reproducing the gate's literal historical failure), the drain-then-close shape does not. - tests_py/scripts/test_docker_smoke_client.py pins that docker_smoke_client's exchange never closes stdin before both expected ids are read, for its own three-frame batch. Corroborating measurement (not the gate, since the race is probabilistic): 20/20 real `docker build` + docker_smoke.sh runs passed locally against the fixed image, versus the historical ~1-in-5 failure rate on the old script (main run at 12:29 today, PRs #423-425). Co-Authored-By: Claude <noreply@anthropic.com>
… fix git merge-base HEAD origin/main was 4 commits behind origin/main — rebased first (per PR #423-425's shared cause), then re-ran the CI's exact invocation, `check_craftsmanship.py --base origin/main`, which diffs against the base ref's baseline rather than the working tree's and caught three real violations `check_craftsmanship.py` alone (no --base) does not: - scripts/docker_smoke_client.py::main exceeded the 40-line method cap (CLAUDE.md's local tightening of coding-standards.md §4.2) — split into _build_parser()/_evaluate()/main(), each under the limit. - scripts/docker_smoke_client.py::TOOLS_LIST_ID (value 3) had no `# source:` comment — added one, same source as PROTOCOL_VERSION above it (docker_smoke.sh's original REQUESTS heredoc, where tools/list was request id=3). - tests_py/infrastructure/test_stdio_eof_drain.py grew to 318 lines, over the 300-line file cap, after the prior commit appended the docker-smoke-specific pinning test to it. Split that test class into a new sibling file, test_docker_smoke_stdio_eof_drain.py, which imports (not duplicates) _server/_GatedWriteStream/_collect from the original — both files now under the cap, no test content lost. No production logic changed: pytest (1448 passed / 35 skipped), ruff check/format, and shellcheck all still clean; the deterministic reproduction (real mcp SDK, docker_smoke's own id=3/tools/list request, write-then-close loses it / drain-then-close does not) is unchanged, just relocated. Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ci): close docker_smoke.sh's stdin-before-drain race in the bare-container gate docker_smoke.sh drove the container with `printf '%s' "$REQUESTS" | docker run --rm -i ...`: printf closes its end of the pipe (the container's stdin) the instant the batch is written, before any response has been read. That is the exact anti-pattern PR #331 fixed in scripts/mcp_host_client.py for a local subprocess: closing stdin is the MCP shutdown signal (2025-06-18 SS Lifecycle -> Shutdown -> stdio), not an end-of-input marker, and mcp 2.0.0's _handle_request drops an in-flight response write rather than deliver it once EOF fires the cancel scope. Verified against jsonrpc_dispatcher.py that this cancellation path is method-agnostic, so tools/list (id=3) is exactly as vulnerable as any other request — matching this gate's observed signature ("no valid tools/list response (id=3)", empty stderr, no JSON-RPC error frame) and its history of intermittent failures on unrelated PRs and on main itself. Fix: scripts/docker_smoke_client.py drives the container the same way mcp_host_client.py drives a local server — keep stdin open until every expected response id has arrived (mcp_host_client.drain_exchange, a new generic primitive extracted from _exchange, behavior-preserving), close it only then. docker_smoke.sh now builds the image and delegates the run+exchange+assert sequence to this module; the watchdog still docker-kills the container via --cidfile on the same 60s deadline (fires at most once, never retries). Deterministic reproduction (no clock, no docker, no retries): - tests_py/infrastructure/test_stdio_eof_drain.py adds TestDockerSmokeToolsListLostBeforeDrain, using docker_smoke's own id=3/tools/list request against the real mcp 2.0.0 SDK: the write-then-close-before-drain shape drops the response (reproducing the gate's literal historical failure), the drain-then-close shape does not. - tests_py/scripts/test_docker_smoke_client.py pins that docker_smoke_client's exchange never closes stdin before both expected ids are read, for its own three-frame batch. Corroborating measurement (not the gate, since the race is probabilistic): 20/20 real `docker build` + docker_smoke.sh runs passed locally against the fixed image, versus the historical ~1-in-5 failure rate on the old script (main run at 12:29 today, PRs #423-425). Co-Authored-By: Claude <noreply@anthropic.com> * fix(ci): satisfy the base-ref craftsmanship gate for the docker_smoke fix git merge-base HEAD origin/main was 4 commits behind origin/main — rebased first (per PR #423-425's shared cause), then re-ran the CI's exact invocation, `check_craftsmanship.py --base origin/main`, which diffs against the base ref's baseline rather than the working tree's and caught three real violations `check_craftsmanship.py` alone (no --base) does not: - scripts/docker_smoke_client.py::main exceeded the 40-line method cap (CLAUDE.md's local tightening of coding-standards.md §4.2) — split into _build_parser()/_evaluate()/main(), each under the limit. - scripts/docker_smoke_client.py::TOOLS_LIST_ID (value 3) had no `# source:` comment — added one, same source as PROTOCOL_VERSION above it (docker_smoke.sh's original REQUESTS heredoc, where tools/list was request id=3). - tests_py/infrastructure/test_stdio_eof_drain.py grew to 318 lines, over the 300-line file cap, after the prior commit appended the docker-smoke-specific pinning test to it. Split that test class into a new sibling file, test_docker_smoke_stdio_eof_drain.py, which imports (not duplicates) _server/_GatedWriteStream/_collect from the original — both files now under the cap, no test content lost. No production logic changed: pytest (1448 passed / 35 skipped), ruff check/format, and shellcheck all still clean; the deterministic reproduction (real mcp SDK, docker_smoke's own id=3/tools/list request, write-then-close loses it / drain-then-close does not) is unchanged, just relocated. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
|
ZETETIC-REVIEW: APPROVE Head reviewed: Move 0 — Ledger reconciliation / seen-defect checkSingle hunk, single file, one line changed (digest only). No branches, no error arms, no rationalization language in the diff or the dependabot-generated description. Pass. Stakes (Move 7)Low — dev-only container image, digest-only bump, no runtime logic, no production path. Criterion: What changedPython major.minor unchanged (3.14 → 3.14). No other lines touched. Verification performed on the current head
Rules compliance
VerdictAPPROVE. Digest change verified independently against the live registry, base is fresh, both repo gates pass clean, diff is scope-clean, CI green. The pre-existing |
Bumps python from
cea0e60toa7fb1e6.