A batch of independent, small correctness defects across the actions. None is individually urgent; together they account for most of the "silent wrong behaviour" found in the 2026-07 maintenance audit. Each is a few lines.
setup-environment
Container-mode conda env update omits -n, so a delta env file can update the wrong environment — action.yml:77. Standard mode passes -n ${{ inputs.environment-name }} at :139; container mode does not, so conda resolves the target from the file's own name: key. A lecture repo whose delta file says name: lecture-python gets /opt/conda/envs/lecture-python, which is not on the container PATH — the step prints "✅ Environment updated successfully" and the build fails much later with ModuleNotFoundError. Fix: conda env update -n "$ENV_NAME" -f "$ENV_UPDATE", move the path into env: rather than interpolating into the shell, and warn if the file's name: differs.
LaTeX install dies with no output on an empty requirements file, and passes trailing comments to apt — action.yml:147. Under -o pipefail a fully-commented file makes the second grep exit 1 and kills the step with zero diagnostics. grep -v '^#' also strips only whole-line comments, so texlive-xetex # for unicode math reaches apt verbatim; $PACKAGES is unquoted at :150. Fix: sed -e 's/#.*//' into a mapfile array, warn-and-skip if empty, install -y "${pkgs[@]}".
The environment summary reports the wrong Python environment in standard mode — action.yml:163 and :201. The step uses plain bash, correct for containers, but setup-miniconda activates via the login profile — which is why :135 uses bash -l {0}. On a standard runner pip list reports the runner's default Python, directly below a confident "Conda: Restored from cache ✅". Fix: branch on container-mode, or invoke "$CONDA/envs/$ENV_NAME/bin/python" -m pip list explicitly, and drop the 2>/dev/null.
build-lectures
The build-failure banner names an artifact that doesn't exist and the wrong reports directory — action.yml:156, :159, :160. The banner hardcodes execution-reports-{builder} but :172 honours failure-artifact-name, which test-containers-lectures.yml:84 always sets — so every failing container-validation run prints a wrong download instruction. Line 160 also sends pdflatex users to _build/pdflatex/reports/; the real path is _build/latex/reports per the case at :125. Fix: compute both strings once in env: and echo those. PLAN item 15 flags the path half only.
build-jupyter-cache
builders is validated and parsed by substring, so unknown builders are silently dropped — action.yml:89, :103-124. The input is documented as comma-separated but never split. builders: 'html,pdf' passes validation and builds HTML only with no warning; 'nojupyter' enables the jupyter build. The same three tests are repeated three times across two steps. Fix: split on commas once, case each trimmed token against html|pdflatex|jupyter, error on anything else, and emit the three run-* outputs from that loop.
"Fail if builds failed" is implicitly gated on success() — action.yml:369. An if: with no status function gets an implicit success() &&, so once the issue-creation step at :285 errors — gh absent in-container, or no issues: write — the only ::error::One or more builds failed annotation is never emitted, and the surfaced cause points at the notification plumbing instead of the build. Fix: if: always() && … plus continue-on-error: true on the issue step.
The failure-notification path aborts on ordinary input and on a missing label — create-failure-issue.sh:81, :95-99. ASSIGNEES_FLAG="--assignee $ISSUE_ASSIGNEES" is deliberately unquoted, so 'alice, bob' becomes --assignee alice, plus a stray positional and gh exits non-zero under set -e. And gh issue create --label fails hard when a label does not exist — the default build-failure,automated is not a GitHub default, so the first failure on a fresh repo produces no issue at all. Fix: build assignee args as an array; retry without --label (with a ::warning::) on failure.
The auto-created failure issue gives an unrunnable repro command — create-failure-issue.sh:66 suggests jb build $SOURCE_DIR --builder <failed-builder>; for the jupyter builder the real invocation is --builder=custom --custom-builder=jupyter -n per build-lectures/action.yml:104, and --path-output is missing in all cases.
publish-gh-pages
asset-url is bound to an output action-gh-release does not expose — action.yml:32. softprops/action-gh-release exposes url, id, upload_url, assets — never asset-url — so the output is always empty while README.md:42 advertises it. Fix: delete the output and the README row (PLAN item 7), or repoint at fromJSON(steps.upload-release.outputs.assets)[0].browser_download_url.
scripts/
Renamed lecture files never get a preview deep link — detect-changed-lectures.sh:37-40. git diff --name-status reports renames as R<score> with two tab-separated paths; the *) continue arm drops them, so a renamed lecture — a routine editorial change — is missing from the preview comment. Fix: pass --no-renames on :29 so renames decompose into A/D pairs.
Checklist
Advances #83 (issue-body/artifact alignment) and PLAN items 14/15.
A batch of independent, small correctness defects across the actions. None is individually urgent; together they account for most of the "silent wrong behaviour" found in the 2026-07 maintenance audit. Each is a few lines.
setup-environmentContainer-mode
conda env updateomits-n, so a delta env file can update the wrong environment —action.yml:77. Standard mode passes-n ${{ inputs.environment-name }}at:139; container mode does not, so conda resolves the target from the file's ownname:key. A lecture repo whose delta file saysname: lecture-pythongets/opt/conda/envs/lecture-python, which is not on the containerPATH— the step prints "✅ Environment updated successfully" and the build fails much later withModuleNotFoundError. Fix:conda env update -n "$ENV_NAME" -f "$ENV_UPDATE", move the path intoenv:rather than interpolating into the shell, and warn if the file'sname:differs.LaTeX install dies with no output on an empty requirements file, and passes trailing comments to apt —
action.yml:147. Under-o pipefaila fully-commented file makes the secondgrepexit 1 and kills the step with zero diagnostics.grep -v '^#'also strips only whole-line comments, sotexlive-xetex # for unicode mathreaches apt verbatim;$PACKAGESis unquoted at:150. Fix:sed -e 's/#.*//'into amapfilearray, warn-and-skip if empty,install -y "${pkgs[@]}".The environment summary reports the wrong Python environment in standard mode —
action.yml:163and:201. The step uses plainbash, correct for containers, butsetup-minicondaactivates via the login profile — which is why:135usesbash -l {0}. On a standard runnerpip listreports the runner's default Python, directly below a confident "Conda: Restored from cache ✅". Fix: branch oncontainer-mode, or invoke"$CONDA/envs/$ENV_NAME/bin/python" -m pip listexplicitly, and drop the2>/dev/null.build-lecturesThe build-failure banner names an artifact that doesn't exist and the wrong reports directory —
action.yml:156,:159,:160. The banner hardcodesexecution-reports-{builder}but:172honoursfailure-artifact-name, whichtest-containers-lectures.yml:84always sets — so every failing container-validation run prints a wrong download instruction. Line 160 also sends pdflatex users to_build/pdflatex/reports/; the real path is_build/latex/reportsper thecaseat:125. Fix: compute both strings once inenv:and echo those. PLAN item 15 flags the path half only.build-jupyter-cachebuildersis validated and parsed by substring, so unknown builders are silently dropped —action.yml:89,:103-124. The input is documented as comma-separated but never split.builders: 'html,pdf'passes validation and builds HTML only with no warning;'nojupyter'enables the jupyter build. The same three tests are repeated three times across two steps. Fix: split on commas once,caseeach trimmed token againsthtml|pdflatex|jupyter, error on anything else, and emit the threerun-*outputs from that loop."Fail if builds failed" is implicitly gated on
success()—action.yml:369. Anif:with no status function gets an implicitsuccess() &&, so once the issue-creation step at:285errors —ghabsent in-container, or noissues: write— the only::error::One or more builds failedannotation is never emitted, and the surfaced cause points at the notification plumbing instead of the build. Fix:if: always() && …pluscontinue-on-error: trueon the issue step.The failure-notification path aborts on ordinary input and on a missing label —
create-failure-issue.sh:81,:95-99.ASSIGNEES_FLAG="--assignee $ISSUE_ASSIGNEES"is deliberately unquoted, so'alice, bob'becomes--assignee alice,plus a stray positional andghexits non-zero underset -e. Andgh issue create --labelfails hard when a label does not exist — the defaultbuild-failure,automatedis not a GitHub default, so the first failure on a fresh repo produces no issue at all. Fix: build assignee args as an array; retry without--label(with a::warning::) on failure.The auto-created failure issue gives an unrunnable repro command —
create-failure-issue.sh:66suggestsjb build $SOURCE_DIR --builder <failed-builder>; for the jupyter builder the real invocation is--builder=custom --custom-builder=jupyter -nperbuild-lectures/action.yml:104, and--path-outputis missing in all cases.publish-gh-pagesasset-urlis bound to an outputaction-gh-releasedoes not expose —action.yml:32.softprops/action-gh-releaseexposesurl,id,upload_url,assets— neverasset-url— so the output is always empty whileREADME.md:42advertises it. Fix: delete the output and the README row (PLAN item 7), or repoint atfromJSON(steps.upload-release.outputs.assets)[0].browser_download_url.scripts/Renamed lecture files never get a preview deep link —
detect-changed-lectures.sh:37-40.git diff --name-statusreports renames asR<score>with two tab-separated paths; the*) continuearm drops them, so a renamed lecture — a routine editorial change — is missing from the preview comment. Fix: pass--no-renameson:29so renames decompose intoA/Dpairs.Checklist
conda env update -nbuild-jupyter-cacheif: always() &&on the final fail stepcreate-failure-issue.shasset-url--no-renamesindetect-changed-lectures.shAdvances #83 (issue-body/artifact alignment) and PLAN items 14/15.