Repository navigation
ci(phase-validation): the summary step SyntaxErrors on every run, and a quote-blind unescape reports it clean on CI #15152
Description
Verdict
phase_validation.yml's summary step dies with a SyntaxError on every run under the Python the workflow pins, and prints Results not available instead of the maturity figure. The import-smoke guard reports that same program clean on CI, because _unescape_shell deletes the exact backslashes that break it.
Two defects, one of which is the reason the other is invisible.
1. The workflow step is broken at runtime
.github/workflows/phase_validation.yml:126 runs a single-quoted python3 -c. sh performs no escape processing inside '...', so the \" sequences reach the interpreter as backslashes — and a backslash inside an f-string replacement field is a SyntaxError on every Python version, including 3.12+ where PEP 701 relaxed the surrounding rules.
Run verbatim under bash with the pinned interpreter (setup-python at phase_validation.yml:52 pins 3.14):
SyntaxError: unexpected character after line continuation character
With the step's own error handling attached (2>/dev/null || echo "Results not available"), the observed output is:
Results not available
So the job summary has been reporting the fallback string in place of the maturity figure, silently, for as long as the line has existed. This is a defect in what a workflow reports and does not depend on any test.
2. _unescape_shell is quote-blind, which manufactures a false green
repo_tests/test_ci_import_smoke_paths_14252.py's _unescape_shell applies double-quote shell unescaping unconditionally, including to single-quoted programs that sh never escape-processes. It rewrites the program into something the step does not run — and rewrites it in the direction that hides the bug.
Measured against the unmodified base, same checkout, same workflow file:
| Interpreter | unparseable programs the guard reports | imports it extracted for the broken step |
|---|---|---|
| 3.10 | [('phase_validation.yml', …)] |
[] |
| 3.14 | [] |
['json'] |
On the interpreter CI actually runs, the guard reports zero unparseable programs and claims to have checked json for a step that cannot start. A guard that rewrites its subject into something that passes is indistinguishable from a guard that passes.
The PEP 701 measurement — record this so nobody re-derives it
Two different strings are involved, and conflating them is what made this hard to see.
- A — what
shhands the interpreter (backslashes intact):… print(f"…{r.get(\"overall_maturity\", 0):.1f}%") - B — what the extractor produces after quote-blind unescaping (
\"→"):… print(f"…{r.get("overall_maturity", 0):.1f}%")
| 3.10 | 3.11 | 3.12 | 3.14 | |
|---|---|---|---|---|
| A (runtime truth) | SyntaxError: f-string expression part cannot include a backslash | SyntaxError: same | SyntaxError: unexpected character after line continuation character | SyntaxError: same |
| B (extractor output) | SyntaxError: f-string: unmatched ( |
SyntaxError: same | parses | parses |
A fails on every version. B is version-dependent — illegal before 3.12, legal from 3.12 under PEP 701's relaxation of nested quotes.
Consequences worth stating plainly:
- CI runs 3.12/3.14, so shard 12 is green on the base. Any claim that this guard is red on
Dev_new_gui, or that its failure is "environment-independent — no package, Python version or checkout layout is involved", is wrong. It was red only on a 3.10 checkout. This is precisely the trap chore(devenv): local verification runs fastapi 0.135.2 against a declared floor of >=0.141.1 — a local pass carries no information about CI #15091 records: a version-dependent failure whose message carries no version, read as environment-independent. - The regex
_INLINE_PYTHONdoes have escape-awareness ((?:\\.|(?!\1).)*) and the capture is not truncated — verified: the capture ends on%")and the next character after the match is a space. The fragment that appears in the failure message iscode[:60], display truncation in the assertion itself. Its docstring still describes the pre-fix behaviour and should be corrected; that stale text is what produced two independent mis-diagnoses.
Acceptance criteria
phase_validation.yml's summary step prints the maturity figure under the pinned interpreter, demonstrated by running the line._unescape_shellapplies double-quote rules only to double-quoted programs; a single-quoted program is returned exactly asshdelivers it. Asserted directly, not via the workflow that happens to exercise it._imported_namescannot report "no imports" for input it could not read.- Every
python -csite in every workflow is captured as one whole shell argument, asserted — a site the pattern cannot close on currently produces no scan entry at all. - The stale docstrings on
_INLINE_PYTHONandtest_every_extracted_program_parsesare corrected, and the parse-failure message names the interpreter version so a version-dependent failure cannot again read as environment-independent.
Related
- bug(ci): deployment-check has been red on every Dev_new_gui run for days — it imports a module path that does not exist #14252 — the guard and its original
coverage.ymlinstance. - chore(devenv): local verification runs fastapi 0.135.2 against a declared floor of >=0.141.1 — a local pass carries no information about CI #15091 — a local pass carrying no information about CI; this is the same trap in the opposite direction.
- ci(base-red): phase_validation.yml inline python is unparseable — import smoke verifies nothing, shard 12 red on Dev_new_gui #15122 — closed
not_planned; its premise (base red, environment-independent) is corrected above. The two defects here are what survives it.
Activity
fix(ci): stop a broken workflow step reporting clean, and the guard r…
fix(ci): stop a broken workflow step reporting clean, and the guard r…
github-actions commentedon Aug 28, 2026
PR #15128 (merged to
Dev_new_gui) references this issue with a close keyword.If this issue is fully resolved, close it manually. If work remains, no action is needed.
mrveiss commentedon Aug 28, 2026
Closing — verified against the merged base, not the PR diff
Delivered by #15128, merged as
6af2946cconDev_new_gui. All references read fromorigin/Dev_new_gui..github/workflows/phase_validation.yml:130is now backslash-free — the value is bound tomfirst and formatted with"{:.1f}%".format(m). I ran that exact single-quoted argument throughsh -c, against aphase_validation_results.jsonholdingoverall_maturity: 73.25, on the pinned3.14.6(phase_validation.yml:52pins3.14) and on 3.12.13 / 3.10.12. All three print**Overall System Maturity:** 73.2%; none hits theResults not availablefallback_unescape_shellapplies double-quote rules only to double-quoted programs, asserted directlyrepo_tests/test_ci_import_smoke_paths_14252.py:95-112returnscodeunchanged unlessquote == '"'; asserted directly at:293test_a_single_quoted_program_keeps_its_backslashes, which checks both branches on the same literal — identity under', unescaped under"_imported_namescannot report "no imports" for input it could not read:154-172parses without atry, so a SyntaxError leaves at the call site; asserted at:305test_an_unreadable_program_raises_rather_than_reporting_no_importsviapytest.raises(SyntaxError)python -csite in every workflow captured as one whole shell argument, asserted_INLINE_PYTHON_INVOCATION(:52) enumerates every invocation ignoring quoting,_ARGUMENT_END(:56) proves the capture ended on a real argument boundary,_uncaptured_invocations(:126) reports both "no match at all" and "capture ends mid-argument"; asserted at:263test_no_python_dash_c_invocation_escapes_the_extractor. A heredoc form is handled separately (_HEREDOC_PYTHON,:59)_INLINE_PYTHONcomment block (:38-51) now namescode[:60]as display truncation in the assertion rather than describing the capture as truncated;test_every_extracted_program_parses(:236) documents the PEP 701 version dependence, and its failure message interpolatessys.version_info.major/minor(:255-260) with an explicit instruction to confirm on the version CI runs before calling it a base failureThe false green is also pinned directly:
test_phase_validation_inline_programs_are_actually_checked(:279) requires that workflow's programs both parse and yield imports, so the step cannot be made green by rendering it invisible to the extractor.What is not proven here
phase_validationworkflow run's job summary onDev_new_gui. The first real run of that workflow after this merge is where the maturity figure should be visible in place ofResults not available; that observation is still outstanding.