Conversation
Commit e82e304 capped the LaTeX rerun loops at five extra passes, but it edited the generated tex.mk only and left tex.mk.nw with the original unbounded while-grep loops. Every other .mk here tangles byte-for-byte from its .nw, so tex.mk was the one file whose generated form no longer matched its source. That is a trap rather than a cosmetic drift: noweb.mk supplies a %.mk: %.mk.nw rule and make remakes included makefiles by itself, so the next build that touched tex.mk.nw would have regenerated tex.mk and silently reverted #73. Move the loop, and the comment explaining why it is bounded, into the chunks they belong to. tex.mk itself is unchanged: tangling tex.mk.nw now reproduces the committed file exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rules producing a .pytxmcr, a .pdf and a .dvi each listed the plain
name and the ${TEX_OUTDIR}/ one as targets of a single pattern rule.
Those two names are alternatives -- two ways of asking for the same
output -- but that is not how make reads them: a pattern rule with
several targets means one recipe that produces all of them (GNU Make
manual, Sect. 10.5.1). Since GNU Make 4.4 it says so, with
a "pattern recipe did not update peer target" warning for every listed
target the recipe did not write. Building nytid's documentation hit it
once per build, from the PythonTeX rule, whose recipe only ever writes
inside ${TEX_OUTDIR}.
Write one single-target rule per name instead, sharing each recipe
through a chunk so the two copies cannot drift. The .pytxmcr rules keep
their double colon: on a pattern rule that marks the rule terminal, and
splitting the targets must not quietly change that.
The rules make ends up with are the same in every configuration. Where
TEX_OUTDIR names a separate directory -- the ltxobj default -- the two
patterns stay distinct and each name selects its own rule. Where it is
set to ".", they coincide; since the recipes are identical the behaviour
is unchanged, just as before, when make saw the same pattern listed
twice within one rule.
Fixes #83.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
COMPILE.tex ran latexmk, then the bounded rerun loop, with a ";" between them. A recipe's exit status is its last command's, and the last command is the loop -- which ends in a break as soon as the log stops asking for another run, and break succeeds. latexmk's status was therefore thrown away, and make called every failed build a success. None of that is cosmetic: a build that cannot fail cannot be observed to have failed. nytid's documentation build exited zero for a long time with an "Emergency stop" and a bibtex "Error 2" standing in its log, so neither its author nor its continuous integration ever learnt that the document had stopped building (dbosk/nytid#446). Every kind of LaTeX breakage short of one that removes an already-built file was hidden from make in the same way. Join the compilation to the loop with "&&" instead. The loop's grep only has a log to read when the run got far enough to write one, so a failed run has nothing to enter the loop for and its own status becomes the recipe's. The reruns inside the loop each end with "|| exit $?" for the same reason: a for loop runs its body to the end no matter what fails inside it, so a rerun that died would still be followed by a successful break. What make reports is latexmk's own status rather than a flattened 1 -- a document with a missing \input now fails with "Error 12". The DVI rerun loop had the same hole and gets the same guard. Its first run is a recipe line of its own, so make already stopped on that one. Fixes #85. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two leftovers: one from #83, one from the move to an output directory. The .dtx rules producing a .pdf and a .dvi still listed the plain name and the ${TEX_OUTDIR}/ one as targets of a single pattern rule -- the shape 55a6efb took out of the .tex and .pytxmcr rules. Give each name its own rule here as well, sharing each recipe through a chunk so the copies cannot drift; the .dvi pair reuses the document recipe outright, since compiling a class to DVI is the same work. These rules carry a single colon, so unlike the .pytxmcr pair there is no terminal-rule property for the split to preserve. Under GNU Make 4.4 a package project asking for the ${TEX_OUTDIR}/ name got one "pattern recipe did not update peer target" warning from each rule, and so did one asking for the plain name with TEX_OUTDIR set to the empty string; it now gets none, and byte-identical output in every TEX_OUTDIR configuration. The bibtex rule ended with "mv $@ ${@:.bbl=.blg} ${TEX_OUTDIR}". bibtex has no output-directory option; what it does is write its .bbl and .blg beside the .aux it was handed, and the .aux we hand it is ${TEX_OUTDIR}/%.aux -- so both already are where the target says they should be. With $@ carrying the ${TEX_OUTDIR}/ prefix, the line asked mv to move each file onto itself; mv declines that ("are the same file"), and as the recipe's last command the refusal failed the rule. Drop it. Repairing the names with $(notdir ...) would not help, as it would name files that are no longer anywhere to be moved from. Fixes #86. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d8dc836 made every non-zero latexmk status fail the rule. That turns out to be too blunt. latexmk spends a single status -- 12 -- on a document with an error in it and on a document that simply never settles, and the two want opposite answers. Measured on TeX Live 2025: a missing \input, an undefined control sequence and an oscillating layout all exit 12. Sort the outcomes into three instead. A clean run succeeds silently. A run latexmk itself blames on running out of reruns -- it says "Maximum runs ... reached without getting stable files" and reports nothing else wrong -- prints a loud, greppable warning and carries on into the bounded rerun loop of #73, which exists for exactly this case and which d8dc836 had made unreachable. Anything else still fails, with latexmk's status. "Nothing else wrong" is two greps, and they are what keeps this from being the old masking in a new disguise. A run whose bibliography failed leaves a "Collected error summary"; one whose -use-make delegation ran a rule that failed leaves a "make[1]: *** [...] Error" behind. Either fails the rule even when latexmk also ran out of reruns -- and that combination is exactly the shape of dbosk/nytid#455. The second grep asks for a failed *recipe*, not merely for make having been unhappy. On a first pass make is routinely unhappy and rightly so: latexmk hands it the name of every file LaTeX reported missing, the ones LaTeX is about to write itself included, and make answers "No rule to make target 'nytid.toc'" with nothing at all wrong. A rule that ran and came back non-zero is the different thing, and it reads differently. Telling the cases apart needs latexmk's words rather than its status, so each run is teed to a file next to the .log with its status put aside separately: a pipeline reports tee's status, not latexmk's. clean-tex removes both files, since latexmk's own -C does not know about them. The motivating document is nytid's, which contains a genuine two-cycle page-break oscillation: a break moves one table-of-contents entry between two pages and drags some thirty noweb sublabels after it, so latexmk cannot converge whatever its rerun budget -- nine runs were not enough. Under d8dc836 that correct document could not be built at all. The DVI path gets the same treatment and becomes one recipe line, because exit ends only the shell of the line it is written in. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #85. Fixes #86. Stacked on #84 (base branch of this PR); merge #84
first, then this.
;, so make reportedsuccess for failed builds — this masked dbosk/nytid's broken doc build for
a long time. latexmk's exit status now propagates (also in the DVI rerun
loop).
failure (measured on TeX Live 2025: missing \input, undefined macro, and
non-convergence all exit 12), so exit-code discrimination would re-mask
broken documents. The recipe instead reads latexmk's verdict: real error →
fail with latexmk's status; "Maximum runs … without getting stable files"
with no collected errors and no failed sub-make recipe → loud greppable
warning + success (motivating case: dbosk/nytid's 1100-page document has a
provable two-state page-break oscillation, so no run budget ever
satisfies the stability check); converged → silent. Validated against 8
fixtures including mock traps (error-summary+oscillation and
failed-recipe+oscillation both still fail; benign first-pass "No rule to
make target" noise from -use-make is not mistaken for failure).
.dtxmulti-target pattern rules are split per target (GNUMake 4.4 peer-target warnings: 4 configurations before, 0 after,
byte-identical outputs), and the bibtex rule's mv-onto-itself is deleted —
empirically, bibtex writes
.bbl/.blgbeside the.auxit is handed(openout_any=p forbids
../), so the outputs already land on the targetand the issue's suggested
$(notdir …)would name files that don't exist.🤖 Generated with Claude Code
https://claude.ai/code/session_01AhWZp3q1126cvw9bW8VBB5