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
Revised twice — current as of 2026-07-30. The original audit was wrong, and so was the first correction. Two things had to change: the fix needs set -eo pipefail as well as the flags (field note in the comments), and the audit has to ask "is the firstjb build gated?" rather than "is the sphinx-tojupyter step gated?" — because under execute_notebooks: "cache" only the first build executes anything, and which step that is varies per repo. The corrected table is below.
Summary
A broken notebook can pass CI green in five lecture repos. Two independent defects combine:
Only the first jb build executes.execute_notebooks: "cache" means the first builder in the workflow executes the notebooks and populates the cache; every later builder reads it. So the gate has to be on the firstjb build — and depending on whether the LaTeX step is active, that is either "Build PDF from LaTeX" or "Build Download Notebooks (sphinx-tojupyter)". Gating a later step catches only warnings specific to that builder.
The step's exit code is not jb build's. These steps run three commands under shell: bash -l {0}. GitHub only injects -eo pipefail for the bare shell: bash shorthand; an explicit custom shell spec gets neither -e nor -o pipefail. So a failing jb build is followed by mkdir and the copy, and the step exits with the copy's status. --keep-going makes this worse rather than better: it forces Sphinx to emit output despite the errors, so the files exist, the copy succeeds, and the step reports green.
Both fixes are needed, on the right step. Flags alone gate nothing; set -eo pipefail alone gates nothing; and either applied to a step that reads from cache gates nothing that matters.
Single-command steps are unaffected — their exit code is already the command's.
Audit — verified 2026-07-30, on the "is the first jb build gated?" rule
✅ uses quantecon/actions/build-lectures@v0.8.0, which is immune by design
lecture-python-intro
cache
Build PDF from LaTeX
❌ needs -W + set -eo pipefail
lecture-python-advanced.myst
cache
Build PDF from LaTeX
❌ needs -W + set -eo pipefail
lecture-python.myst
cache
tojupyter
❌ has -W; needs set -eo pipefail
lecture-python-programming
cache
tojupyter
❌ has -W; needs set -eo pipefail
lecture-jax
cache
tojupyter
❌ has -W; needs set -eo pipefail
lecture-python-programming.fa
—
—
n/a — the whole step is commented out
Five repos need changing, not six. Note the first jb build differs: where the LaTeX step is active it runs before the notebooks step, and in the zh-cn repos the LaTeX step is commented out, which is why gating tojupyter is correct there and wrong elsewhere.
Corrections to earlier versions of this audit, for the record: it cleared lecture-python.myst and lecture-python-programming (they are not gated — flags present but masked); it listed the zh-cn pair as the problem (they are now correct); it omitted lecture-jax and lecture-python-advanced.myst; and it wrongly flagged lecture-datascience.myst, which gates fine via its single-command HTML step under execute_notebooks: "force".
Evidence
Surfaced during the lecture-python.zh-cn resync wave (2026-07): a cold Build Cache run reported success while its execution-reports artifact held 19 notebook execution failures, and the resync only stayed safe because of a manual workaround — every fix was pushed as a new commit to force a cold rebuild, and the reports were read by hand.
The exit-code masking was verified empirically: bash -l -c 'false; echo hi' exits 0; with set -eo pipefail it exits 1.
Fix
Apply to the first jb build in each of the five repos: add set -eo pipefail as the first line of the run: block, and ensure the command carries -n -W --keep-going.
For the two where the first build is the LaTeX step (lecture-python-intro, lecture-python-advanced.myst), that means the "Build PDF from LaTeX" step. Reference implementation — QuantEcon/lecture-python-intro#809:
- name: Build PDF from LaTeXshell: bash -l {0}run: | set -eo pipefail jb build lectures --builder pdflatex --path-output ./ -n -W --keep-going mkdir -p _build/html/_pdf cp -u _build/latex/*.pdf _build/html/_pdf
For the three where the first build is the notebooks step (lecture-python.myst, lecture-python-programming, lecture-jax), the flags are already present, so it is a one-line set -eo pipefail insertion. The merged step in lecture-python.zh-cn is the template.
Two caveats. Any lecture with an intentionally-erroring cell needs a raises-exception tag first, or it will — correctly — start failing; worth a grep per repo. And only multi-command run: blocks using an explicit shell: are affected.
This is the tactical fix. The durable answer is migrating these pipelines to quantecon/actions/build-lectures, which captures and propagates the build exit code explicitly and stages files in separate steps — tracked in #348, and blocked on QuantEcon/actions#97 and #98.
Secondary note — cache.yml
The weekly Build Cache job builds with the default HTML builder under execute_notebooks: "cache", which does not catch execution errors even with -W present (the HTML builder reuses the cache rather than re-executing — this is what let the 19 failures above pass). Aligning ci.yml fixes the PR gate, which is the priority; if the cache job should also gate on execution, that is a separate change (run the tojupyter execution builder in cache.yml, or set a raise-on-error config).
References
Found during the intermediate lecture-python.zh-cn resync wave; recorded in project-translation#12.
Important
Revised twice — current as of 2026-07-30. The original audit was wrong, and so was the first correction. Two things had to change: the fix needs
set -eo pipefailas well as the flags (field note in the comments), and the audit has to ask "is the firstjb buildgated?" rather than "is the sphinx-tojupyter step gated?" — because underexecute_notebooks: "cache"only the first build executes anything, and which step that is varies per repo. The corrected table is below.Summary
A broken notebook can pass CI green in five lecture repos. Two independent defects combine:
Only the first
jb buildexecutes.execute_notebooks: "cache"means the first builder in the workflow executes the notebooks and populates the cache; every later builder reads it. So the gate has to be on the firstjb build— and depending on whether the LaTeX step is active, that is either "Build PDF from LaTeX" or "Build Download Notebooks (sphinx-tojupyter)". Gating a later step catches only warnings specific to that builder.The step's exit code is not
jb build's. These steps run three commands undershell: bash -l {0}. GitHub only injects-eo pipefailfor the bareshell: bashshorthand; an explicit custom shell spec gets neither-enor-o pipefail. So a failingjb buildis followed bymkdirand the copy, and the step exits with the copy's status.--keep-goingmakes this worse rather than better: it forces Sphinx to emit output despite the errors, so the files exist, the copy succeeds, and the step reports green.Both fixes are needed, on the right step. Flags alone gate nothing;
set -eo pipefailalone gates nothing; and either applied to a step that reads from cache gates nothing that matters.Single-command steps are unaffected — their exit code is already the command's.
Audit — verified 2026-07-30, on the "is the first
jb buildgated?" ruleexecutejb buildlecture-python.zh-cnlecture-intro.zh-cnlecture-python-programming.zh-cnlecture-datascience.myst-W; no change neededlecture-dpquantecon/actions/build-lectures@v0.8.0, which is immune by designlecture-python-intro-W+set -eo pipefaillecture-python-advanced.myst-W+set -eo pipefaillecture-python.myst-W; needsset -eo pipefaillecture-python-programming-W; needsset -eo pipefaillecture-jax-W; needsset -eo pipefaillecture-python-programming.faFive repos need changing, not six. Note the first
jb builddiffers: where the LaTeX step is active it runs before the notebooks step, and in the zh-cn repos the LaTeX step is commented out, which is why gating tojupyter is correct there and wrong elsewhere.Corrections to earlier versions of this audit, for the record: it cleared
lecture-python.mystandlecture-python-programming(they are not gated — flags present but masked); it listed the zh-cn pair as the problem (they are now correct); it omittedlecture-jaxandlecture-python-advanced.myst; and it wrongly flaggedlecture-datascience.myst, which gates fine via its single-command HTML step underexecute_notebooks: "force".Evidence
Surfaced during the
lecture-python.zh-cnresync wave (2026-07): a cold Build Cache run reported success while its execution-reports artifact held 19 notebook execution failures, and the resync only stayed safe because of a manual workaround — every fix was pushed as a new commit to force a cold rebuild, and the reports were read by hand.The exit-code masking was verified empirically:
bash -l -c 'false; echo hi'exits 0; withset -eo pipefailit exits 1.Fix
Apply to the first
jb buildin each of the five repos: addset -eo pipefailas the first line of therun:block, and ensure the command carries-n -W --keep-going.For the two where the first build is the LaTeX step (
lecture-python-intro,lecture-python-advanced.myst), that means the "Build PDF from LaTeX" step. Reference implementation — QuantEcon/lecture-python-intro#809:For the three where the first build is the notebooks step (
lecture-python.myst,lecture-python-programming,lecture-jax), the flags are already present, so it is a one-lineset -eo pipefailinsertion. The merged step inlecture-python.zh-cnis the template.Two caveats. Any lecture with an intentionally-erroring cell needs a
raises-exceptiontag first, or it will — correctly — start failing; worth a grep per repo. And only multi-commandrun:blocks using an explicitshell:are affected.This is the tactical fix. The durable answer is migrating these pipelines to
quantecon/actions/build-lectures, which captures and propagates the build exit code explicitly and stages files in separate steps — tracked in #348, and blocked on QuantEcon/actions#97 and #98.Secondary note —
cache.ymlThe weekly Build Cache job builds with the default HTML builder under
execute_notebooks: "cache", which does not catch execution errors even with-Wpresent (the HTML builder reuses the cache rather than re-executing — this is what let the 19 failures above pass). Aligningci.ymlfixes the PR gate, which is the priority; if the cache job should also gate on execution, that is a separate change (run the tojupyter execution builder incache.yml, or set a raise-on-error config).References
lecture-python.zh-cnresync wave; recorded in project-translation#12.