You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make _instruction_text in src/cli/managers/base_image_preflight.py keep a line
continuation active across an empty physical line, so a FROM that Docker joins into the
preceding RUN is no longer read as a build stage.
Context you need
_instruction_text blanks the lines of a Dockerfile that only look like instructions
(heredoc payloads, continuations, comments) before _final_stage_base scans what is left for FROM stages. Its whole purpose is that the preflight must never conclude a template is
covered when the image that actually ships is third-party — the module docstring calls a
silent pass "the one thing this module may not do".
The defect. In the continuation branch, an empty line clears continued:
src/cli/managers/base_image_preflight.py:232 — continued = not pending and line.rstrip().endswith(escape)
An empty line does not end with the escape character, so continued becomes False and the
next line is read as an instruction. Docker does not agree: it joins across the empty line
and only emits a NoEmptyContinuation build check warning.
Measured, on 58fcdd74 (head of PR #387 at the time of filing):
fromsrc.cli.managers.base_image_preflightimport_final_stage_baset="FROM docker.io/library/debian:12\nRUN echo x \\\n\nFROM ghcr.io/fasrc/a2rchi-python-base:1.0\n"_final_stage_base(t) # -> 'ghcr.io/fasrc/a2rchi-python-base:1.0'
Docker's real final stage for that template is docker.io/library/debian:12. Verified with docker build --check (Docker 29.5.1, docker/dockerfile:1 frontend) using a payload that
points at a nonexistent registry, so a stage Docker really parses shows up as a metadata
resolution and a stage it swallows does not:
# syntax=docker/dockerfile:1
FROM docker.io/library/busybox:latest
RUN echo x \
FROM nonexistent.invalid/definitely/not-real:1.0
Result: only docker.io/library/busybox:latest metadata is loaded, no error, plus WARNING: NoEmptyContinuation. The bogus FROM was swallowed into the RUN. So the
preflight reports a supported base for a template whose shipped stage is third-party, and the
deployment proceeds without probing the image that actually ships.
Docker's own stance, which you must decide against explicitly: the NoEmptyContinuation check
says empty continuation lines will become an error in a future release. Today they are
permitted. Target today's behavior — a preflight that models a build Docker will not accept
in future is still correct now, and failing closed on a form Docker currently builds would
refuse a working template.
Why it was deferred. Found by the async Codex reviewer at 2026-08-31T08:51:01Z on PR #387
head 58fcdd74 (inline thread id 3892983973). The 2026-08-31 nightly review had already
reached its NIGHTLY_REVIEW_MAX_ROUNDS bound after fixing three findings of the same class
in 90d95296 and 58fcdd74, so it was verified and handed off rather than fixed unattended.
It is a pre-existing gap, not a regression introduced by either of those commits.
Sibling case already fixed, and your model for this one: a comment line inside a
continuation had the identical defect and was fixed in 90d95296 at src/cli/managers/base_image_preflight.py:229. Read that branch first; this fix belongs
beside it.
Constraints
Branch from origin/dev. origin is fasrc/archi; upstream is archi-physics/archi.
Open the PR with gh pr create --repo fasrc/archi --base dev. Never target upstream.
Never commit directly to dev.
TDD: write the failing test first, watch it fail, then the minimum code, then refactor.
Gate must pass before every commit: bash scripts/gate.sh (black 24.10.0 + isort 6.0.1,
then pytest tests/unit/ with diff-cover patch coverage --fail-under=80 vs origin/dev).
Never bypass with --no-verify.
No Co-Authored-By or session trailers on commits in this repo.
Single PR — this is one behavior change, no mechanical churn to separate.
Add a failing test in tests/unit/test_base_image_preflight.py next to test_a_comment_inside_a_continuation_does_not_end_the_continuation, asserting that a
template whose final stage is third-party, followed by RUN echo x \, an empty line, and
a FROM naming a placeable a2rchi base, is reported by templates_missing_base_reference(). Run it and watch it fail.
In _instruction_text, in the if continued: branch, treat an empty (whitespace-only)
line the way the comment case at line 229 is treated: append the blank, continue, and
leave continued untouched.
Decide and encode one edge case deliberately, with a test either way: does an empty line
inside a heredoc payload still terminate anything? It must not — the if pending: branch
runs before the continuation branch and an empty line is only a terminator if the delimiter
is itself empty. Add a regression test proving a heredoc payload containing an empty line
is still fully blanked.
Update the _instruction_text docstring. It currently documents the comment case as
handled; add the empty-line case to the same paragraph so the next reader does not have to
re-derive it. Cite the NoEmptyContinuation decision from "Context you need".
Run the gate, commit, push, open the PR against fasrc/archi:dev.
Commands
# 0. Where does the code live right now?
gh pr view 387 --repo fasrc/archi --json state,headRefName,mergedAt
# 1. Reproduce the defect (run from the repo root; pipe via stdin so you measure the# working tree and not an installed copy of the package)
python - <<'PY'import subprocessprint("SHA:", subprocess.run(["git","rev-parse","HEAD"],capture_output=True,text=True).stdout.strip())from src.cli.managers.base_image_preflight import _final_stage_baset = "FROM docker.io/library/debian:12\nRUN echo x \\\n\nFROM ghcr.io/fasrc/a2rchi-python-base:1.0\n"print("got :", _final_stage_base(t))print("expected: docker.io/library/debian:12")PY# 2. Confirm Docker's behavior yourself rather than trusting this issue
D=$(mktemp -d); mkdir -p "$D/empty"printf'# syntax=docker/dockerfile:1\nFROM docker.io/library/busybox:latest\nRUN echo x \\\n\nFROM nonexistent.invalid/definitely/not-real:1.0\n'>"$D/empty/Dockerfile"
docker build --check "$D/empty"# expect: only busybox metadata loaded, no error, WARNING: NoEmptyContinuation# 3. Red test, then fix, then the gate
python -m pytest tests/unit/test_base_image_preflight.py -q
bash scripts/gate.sh
# 4. Ship
git push -u origin HEAD:<your-branch>
gh pr create --repo fasrc/archi --base dev
Acceptance criteria
_final_stage_base("FROM docker.io/library/debian:12\nRUN echo x \\\n\nFROM ghcr.io/fasrc/a2rchi-python-base:1.0\n") returns docker.io/library/debian:12.
A new test asserting the above fails on the parent commit and passes on yours. State
both results in the PR body.
A regression test proves an empty line inside a heredoc payload does not end the
payload.
test_a_comment_inside_a_continuation_does_not_end_the_continuation, test_a_comment_inside_a_continuation_cannot_open_a_heredoc, test_a_multi_line_continuation_is_skipped_to_its_end, and test_a_real_stage_after_a_continuation_is_still_read all still pass — an empty-line
fix must not change how a non-empty continuation ends.
bash scripts/gate.sh exits 0, with patch coverage 100% on the changed file.
The _instruction_text docstring names the empty-line case and the NoEmptyContinuation decision.
Run command 0 to find out whether the code is on dev or still only on fix/issue-382-placeable-base, branch accordingly, then run command 1 and confirm you see ghcr.io/fasrc/a2rchi-python-base:1.0 — that is the defect. Then read src/cli/managers/base_image_preflight.py:225-242 and the comment-case fix at line 229,
which is the shape your fix should take.
Objective
Make
_instruction_textinsrc/cli/managers/base_image_preflight.pykeep a linecontinuation active across an empty physical line, so a
FROMthat Docker joins into thepreceding
RUNis no longer read as a build stage.Context you need
_instruction_textblanks the lines of a Dockerfile that only look like instructions(heredoc payloads, continuations, comments) before
_final_stage_basescans what is left forFROMstages. Its whole purpose is that the preflight must never conclude a template iscovered when the image that actually ships is third-party — the module docstring calls a
silent pass "the one thing this module may not do".
The defect. In the continuation branch, an empty line clears
continued:src/cli/managers/base_image_preflight.py:232—continued = not pending and line.rstrip().endswith(escape)An empty line does not end with the escape character, so
continuedbecomesFalseand thenext line is read as an instruction. Docker does not agree: it joins across the empty line
and only emits a
NoEmptyContinuationbuild check warning.Measured, on
58fcdd74(head of PR #387 at the time of filing):Docker's real final stage for that template is
docker.io/library/debian:12. Verified withdocker build --check(Docker 29.5.1,docker/dockerfile:1frontend) using a payload thatpoints at a nonexistent registry, so a stage Docker really parses shows up as a metadata
resolution and a stage it swallows does not:
Result: only
docker.io/library/busybox:latestmetadata is loaded, no error, plusWARNING: NoEmptyContinuation. The bogusFROMwas swallowed into theRUN. So thepreflight reports a supported base for a template whose shipped stage is third-party, and the
deployment proceeds without probing the image that actually ships.
Docker's own stance, which you must decide against explicitly: the
NoEmptyContinuation check
says empty continuation lines will become an error in a future release. Today they are
permitted. Target today's behavior — a preflight that models a build Docker will not accept
in future is still correct now, and failing closed on a form Docker currently builds would
refuse a working template.
Why it was deferred. Found by the async Codex reviewer at 2026-08-31T08:51:01Z on PR #387
head
58fcdd74(inline thread id3892983973). The 2026-08-31 nightly review had alreadyreached its
NIGHTLY_REVIEW_MAX_ROUNDSbound after fixing three findings of the same classin
90d95296and58fcdd74, so it was verified and handed off rather than fixed unattended.It is a pre-existing gap, not a regression introduced by either of those commits.
Sibling case already fixed, and your model for this one: a comment line inside a
continuation had the identical defect and was fixed in
90d95296atsrc/cli/managers/base_image_preflight.py:229. Read that branch first; this fix belongsbeside it.
Constraints
origin/dev.originisfasrc/archi;upstreamisarchi-physics/archi.gh pr create --repo fasrc/archi --base dev. Never target upstream.dev.bash scripts/gate.sh(black 24.10.0 + isort 6.0.1,then
pytest tests/unit/with diff-cover patch coverage--fail-under=80vsorigin/dev).Never bypass with
--no-verify.Co-Authored-Byor session trailers on commits in this repo.fix/issue-382-placeable-baseand not yet ondev— branch from that PR's head instead oforigin/dev, and say so in the PR body. If fix(#382): require a service template's final-stage base to be one the preflight can probe #387 has merged, branch fromorigin/devasnormal. Check first:
gh pr view 387 --repo fasrc/archi --json state,headRefName,mergedAt.Plan
Single PR — this is one behavior change, no mechanical churn to separate.
tests/unit/test_base_image_preflight.pynext totest_a_comment_inside_a_continuation_does_not_end_the_continuation, asserting that atemplate whose final stage is third-party, followed by
RUN echo x \, an empty line, anda
FROMnaming a placeable a2rchi base, is reported bytemplates_missing_base_reference(). Run it and watch it fail._instruction_text, in theif continued:branch, treat an empty (whitespace-only)line the way the comment case at line 229 is treated: append the blank,
continue, andleave
continueduntouched.inside a heredoc payload still terminate anything? It must not — the
if pending:branchruns before the continuation branch and an empty line is only a terminator if the delimiter
is itself empty. Add a regression test proving a heredoc payload containing an empty line
is still fully blanked.
_instruction_textdocstring. It currently documents the comment case ashandled; add the empty-line case to the same paragraph so the next reader does not have to
re-derive it. Cite the
NoEmptyContinuationdecision from "Context you need".fasrc/archi:dev.Commands
Acceptance criteria
_final_stage_base("FROM docker.io/library/debian:12\nRUN echo x \\\n\nFROM ghcr.io/fasrc/a2rchi-python-base:1.0\n")returnsdocker.io/library/debian:12.both results in the PR body.
payload.
test_a_comment_inside_a_continuation_does_not_end_the_continuation,test_a_comment_inside_a_continuation_cannot_open_a_heredoc,test_a_multi_line_continuation_is_skipped_to_its_end, andtest_a_real_stage_after_a_continuation_is_still_readall still pass — an empty-linefix must not change how a non-empty continuation ends.
bash scripts/gate.shexits 0, with patch coverage 100% on the changed file._instruction_textdocstring names the empty-line case and theNoEmptyContinuationdecision.fasrc/archi:dev, noCo-Authored-Bytrailer, and referencesPR fix(#382): require a service template's final-stage base to be one the preflight can probe #387 and this issue.
Start here
Run command 0 to find out whether the code is on
devor still only onfix/issue-382-placeable-base, branch accordingly, then run command 1 and confirm you seeghcr.io/fasrc/a2rchi-python-base:1.0— that is the defect. Then readsrc/cli/managers/base_image_preflight.py:225-242and the comment-case fix at line 229,which is the shape your fix should take.