The step ran `check_project_rules.py && check_emphasis.py` with more
commands on following lines. Under `bash -e` the left operand of `&&`
is an exempt context, so a rules failure did NOT abort the step: it
short-circuited past check_emphasis.py, execution continued to the next
line, and the step exited with that last command's status -- green.
Demonstrated:
bash -e -c 'false_check && emphasis
next_cmd' -> exit 0 (failure swallowed)
bash -e -c 'false_check
emphasis
next_cmd' -> exit 1 (correctly blocks)
So the repo's main content gate could fail while CI stayed green, and
today's new CJK emphasis gate was skipped entirely whenever it did.
Splitting the line restores fail-fast for both.
Swept all 14 repos: only this one and the other CI whose && line is
followed by further commands were affected; the 12 where the && list is
the last line of its run block already exit non-zero and are unchanged.
Verified: YAML parses, both checkers pass, repo test suite unchanged.
check_project_rules.py only checks fences + local links, so a bold marker that
CJK punctuation stops from rendering (e.g. `**(配比)**相互交织` — the closer is
preceded by punctuation and followed by a letter, so it can't close and the
asterisks print literally) would pass this repo's own CI. That class of bug had
to be swept out of every book by hand; nothing here was watching for it.
Adds check_emphasis.py — a standalone gate (identical across all books) that
runs CommonMark's flanking + process-emphasis rules and flags markers that
print literally or silently bold the wrong span. The ci.yaml content step now
runs it right after check_project_rules.py. Verified clean on this book.
Neither step did what its name said, and together they meant no Dependabot PR
was ever auto-merged — the job died before reaching the merge step.
1. "Confirm required checks are configured" failed OPEN. It read branch
protection, which GITHUB_TOKEN cannot do (administration scope is not even
a valid permissions key), so gh api returned 403 JSON into REQUIRED and
`[ "$REQUIRED" -eq 0 ]` died with "integer expression expected". A failing
test inside an `if` condition is exempt from set -e, so the guard evaluated
false and let execution continue. A guard whose whole purpose is refusing
an unsafe merge silently passed whenever it could not check.
2. "Approve low-risk Dependabot PR" can never succeed: GitHub Actions is not
permitted to approve pull requests. It is also unnecessary — these repos do
not require reviews.
The real gate is now branch protection, which as of today requires BOTH
check-commit-identity and CI on every repo, and gh pr merge --auto cannot
complete until they pass. That is stronger than the removed probe, which only
checked that some required check existed.
This makes the 13 repos match oc_guide, which already had exactly this shape
and was the only one whose auto-merge was not broken.
prompt_engineering_guide asserted the old step ORDER; its test now asserts the
invariants that still hold — no self-approve attempt, and auto-merge gated on
the low-risk update-type allowlist.
The mdPress PDF step drives headless Chrome, which intermittently dies with
"websocket url timeout reached" plus dbus/bus.cc:405 errors. Confirmed by the
verbatim signature in six runs across five repos on 2026-07-22 and 07-23 — most
recently it failed a docker_practice smoke test after mdPress had already
finished parsing 196 chapters and assembling the HTML, dying only at step 5/5.
It is infrastructure, not content: a rerun clears it every time.
oc_guide already solved this with tools/build_pdf.py (bounded retries, process-
scoped timeout cleanup, per-attempt logs) and is notably absent from the flake
victims. This applies the same idea to the other 13 repos with a much smaller
change: shadow mdpress with a retry function once per run block, so every call
shape — including the multi-line continuations — is covered without rewriting
42 heterogeneous call sites. `command mdpress` reaches the real binary.
Fails closed: after 3 attempts the function returns 1 and errexit fails the
step. Verified all three paths against a fake mdpress before rollout — first-try
success, success after two flakes, and 3x failure aborting the step without
printing the following command.
oc_guide is untouched (it has no `mdpress build` call). Converging the other 13
onto its richer build_pdf.py remains the better long-term shape.
Per the author: mdPress should default to the latest release.
The cluster had drifted to three versions (0.7.10 x4, 0.7.11 x9, 0.7.14 x1),
and nine of those were pinned to bytes upstream had rebuilt under an existing
tag — the exact thing the harness move to 0.7.14 refused to do on principle.
Pinning also took CI down cluster-wide twice when a tag was rebuilt, because
the digest no longer matched. Latest is now 0.7.15; nothing was on it.
Each of the 43 install sites now resolves the newest release at build time by
following the /releases/latest redirect (no api.github.com call, so no
unauthenticated rate limit on shared runner IPs) and exports the version via
GITHUB_ENV.
Integrity is kept, not dropped: the archive is verified against that same
release's published checksums.txt, and a missing entry aborts the step rather
than passing silently. The honest trade-off is that this verifies the download
rather than pinning an immutable artifact — a rebuilt release is now followed
instead of failing the build. That is the intended behaviour here, since the
rebuild breakage was the problem being solved and upstream is the same author.
Tests updated in step: 12 suites asserted the literal MDPRESS_SHA256 as a
proxy for "this download is checksum-verified". They now assert checksums.txt,
which is where that guarantee lives.
An audit found the guard could miss commits it exists to catch.
1. SIGPIPE. Three inner checks used `printf ... | grep -q` under
`set -euo pipefail`. When grep -q matches early it exits, printf dies of
SIGPIPE, pipefail turns the pipeline non-zero, the `if` goes false, and the
offending commit is silently not reported — the guard then prints its
success line. This file already documents the hazard in a comment and had
fixed it for the two outer pre-filters; the three inner ones were left as
pipes. Now here-strings, like the rest.
Demonstrated on a synthetic revert-style commit (an AI trailer quoted near
the top, then a 200KB body): the pipe form MISSES it, the here-string form
detects it. A trailer in the conventional last position happens to survive
the pipe form, because grep must read to the end before matching — which is
why this never showed up in practice.
2. `chatgpt` was in BAD_NAME and BAD_ANY but not BAD_TRAILER, so a
Co-authored-by naming ChatGPT with an email outside anthropic.com/openai.com
was not caught. Confirmed by test, then added.
Verified before pushing this time: the amended script flags an AI-authored
commit, a large-body early trailer, and a ChatGPT trailer, and scans
claude_guide's full 345-commit history with zero false positives — that repo
has 46 commits with Claude or Anthropic in the subject, none of which are
identity or trailer hits.
A wedged Chrome/mdPress step held a harness runner for 27 minutes yesterday
(normal run: ~4 min) and ignored `gh run cancel`; it only stopped because that
job happened to carry timeout-minutes: 30. An audit found 66 of the 101 jobs
across the cluster had no timeout at all — the same hang there would have held
a runner for GitHub's 6-hour default.
Values come from measured run history, not guesses. Across ~120 successful
runs the slowest workflow tops out at 10 min (Update Preview Publications),
CI at 6.5, Update Preview PDF at 4.8:
30 min — jobs that run Chrome/mdPress/pandoc (3x the observed max, and the
value harness already used)
15 min — release, publish, deploy, check-link, chaincode-tests
10 min — dependabot auto-merge
Every value has at least 6x headroom over its job's observed maximum, so this
should never turn a slow-but-working run into a failure.
Verified: all 72 workflow files still parse, and all 101 jobs now carry an
integer timeout in range.
The guard added earlier could never trigger. tools/render_mermaid.py has no
nonzero exit path — its own docstring said "Exits 0 even if some/all fail
(non-fatal)" — so `if ! render_mermaid.py ...; then exit 1; fi` never ran its
body, and a build that rendered zero diagrams still passed. Reproduced by
running the script with no Chrome on PATH: it printed the fallback warning and
exited 0.
Adds a --strict flag that exits 1 when Chrome is missing or any diagram fails
to render, and passes it from every workflow invocation. The default stays
lenient so local mobile-reader builds keep working, which is what the exit-0
behaviour was actually for.
Verified both directions: with no Chrome, --strict exits 1 and the default
exits 0; against the real book with Chrome, --strict renders every diagram and
exits 0.
Follows the devDependency removal. The build has been mdPress-only since
1.7.0 and nothing referenced these:
- .vuepress/ (config.js + .gitignore). The mirror it configured is still
live, but no workflow in this repo builds or deploys it, so this was a
recipe nobody ran. Local .vuepress/dist/ (400 files of Feb-2026 build
output, never tracked) was removed from the working tree too — with its
.gitignore gone it would otherwise have shown up as untracked noise.
- docker-compose.yml: the vuepress-offline service, plus the now-unused
&mdpress-offline anchor that existed only to feed it.
- README: the 'docker run ... :vuepress' one-liner. That tag was last
pushed 2022-06-25, so readers following it got a four-year-old snapshot
of the book. 'mdpress serve' immediately above it still works.
- CODEOWNERS, .zhlintignore and check_project_rules.py entries for the
deleted directory, and the check-link allowlist entry for the mirror
(no content links to it).
The CHANGELOG entry recording the original CI removal is left alone — it
is history.
Verified: npm test passes (3 + 35), check_project_rules passes 202 files,
docker-compose.yml still parses with no dangling YAML aliases.
Renders Mermaid to SVG via the workflow's Chrome, pandoc --mathml, assembles a
single self-contained responsive HTML reader, and attaches <repo>-<tag>.html to
the GitHub Release next to the PDF. Non-blocking (continue-on-error).