fix(ci): the container gate watched the Dockerfiles and not what they install - #370
Conversation
… install
`docker-scope` decides whether a PR builds the container images, from a regex over the changed
files. It matched `Dockerfile`, `.dockerignore` and `ci.yml` — and nothing else. But a Dockerfile is
not the only build input a Dockerfile has:
services/api/Dockerfile COPY services/api/requirements.lock …
RUN pip install --require-hashes -r /tmp/req/requirements.lock
apps/web/Dockerfile COPY package.json package-lock.json ./
COPY apps/web/package.json …
So a dependency change replaces what the image contains without touching a line the filter watches,
and `Container build (PR, no push)` reports **skipping** — which renders green. Every pip and npm
bump has merged that way: the five web toolchain bumps of #358–#364, the python floors of #368, and
the anthropic 1.x lock recompile of #369 an hour ago. Not one of those greens had built the image
whose dependency set it had just replaced.
This is the same hole `test_container_pr_gate.py` was written to close, arriving through the input
the filter was not watching rather than the one it was — **a skipped job is not a passed job**, and
it reads identically either way.
It is also the same shape as the converter gap that file already records, one level out. That was a
Dockerfile outside the matrix; this is a build input that is not a Dockerfile at all, so it sat
outside the population by KIND rather than by omission — which is why rule 3's converse, added
specifically to catch "a file nobody listed", could not reach it either. **A gate's population is
part of its claim.**
Rule 5 derives the check instead of listing it: read each matrix Dockerfile's own `COPY` lines, keep
the sources whose basename is a dependency manifest, and assert the extracted filter matches every
one. It finds four today — `package.json`, `package-lock.json`, `apps/web/package.json`,
`services/api/requirements.lock` — and the day an image starts copying a new lock file, that path is
covered without editing the test. `--from=` copies are skipped: those come out of an earlier stage,
not the build context, so they are not PR-visible paths.
Mutation-checked by reverting the filter to its previous value: rule 5 fails and names all four
manifests with the image each one builds, rather than failing anonymously. The decoy assertion still
passes, so the widened filter has not become one that matches everything and quietly makes the
`touched=false` branch dead code.
This commit edits `ci.yml`, which the filter already matched, so the container jobs run on its own
PR — the change verifies itself on the way in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here. So far, Strix has reviewed 28 pull requests, surfaced 3 security issues (1 critical/high) and blocked 2 risky merges across this workspace. |
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
Warning Review limit reachedNext included review available in 21 minutes. View limit detailsLimit details: You’ve used all 2 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
docker-scopedecides whether a PR builds the container images, from a regex over the changedfiles. It matched
Dockerfile,.dockerignoreandci.yml— and nothing else. But a Dockerfile isnot the only build input a Dockerfile has:
A dependency change replaces what the image contains without touching a line the filter watches,
so
Container build (PR, no push)reports skipping — which renders green.Every pip and npm bump has merged that way, including the five web toolchain bumps of #358–#364,
the python floors of #368, and the anthropic 1.x lock recompile of #369 an hour ago. None of those
greens had built the image whose dependency set it had just replaced.
This is the hole
test_container_pr_gate.pyexists to close, arriving through the input the filterwas not watching rather than the one it was — a skipped job is not a passed job, and it reads
identically either way.
It is also the converter gap from that same file, one level out. That was a Dockerfile outside the
matrix; this is a build input that is not a Dockerfile at all, so it sat outside the population by
kind rather than by omission — which is why rule 3's converse, added specifically to catch "a
file nobody listed", could not reach it either.
The fix
Rule 5 derives the check rather than listing it: read each matrix Dockerfile's own
COPYlines,keep the sources whose basename is a dependency manifest, and assert the extracted filter matches
every one.
--from=copies are skipped — those come out of an earlier build stage, not the buildcontext, so they are not PR-visible paths.
It finds four today, and the day an image starts copying a new lock file that path is covered
without editing the test:
Mutation-checked by reverting the filter to its previous value — it fails and names them:
The decoy assertion still passes, so the widened filter has not become one that matches everything
and quietly makes the
touched=falsebranch dead code.Self-verifying
This PR edits
ci.yml, which the filter already matched, so the container jobs run here — youshould see
Container build (PR, no push)actually build all three images rather than skip.🤖 Generated with Claude Code