build: guard postinstall; fix: snapshot values in doctest rule#13437
Draft
Planeshifter wants to merge 4 commits into
Draft
build: guard postinstall; fix: snapshot values in doctest rule#13437Planeshifter wants to merge 4 commits into
postinstall; fix: snapshot values in doctest rule#13437Planeshifter wants to merge 4 commits into
Conversation
The weekly `test_published_package` workflow has failed every run since 2026-06-07 with `sh: 1: tools/scripts/apply_patches: not found` (exit 127). Root cause: commit fdbe637 added a `postinstall` hook running `tools/scripts/apply_patches` to patch dev dependencies, but `.npmignore` has excluded `/tools/` from the published npm package since 2021. Any real `npm install` of `@stdlib/stdlib` therefore fails outright, since the referenced script never exists in the installed tree. This commit guards the hook so it silently no-ops when the script is absent, while preserving the original patch-application behavior for contributors who install from a git checkout. Ref: https://github.com/stdlib-js/stdlib/actions/runs/29173854732
The job `Lint Changed Files` on workflow `lint_changed_files` failed on develop with spurious `doctest` errors against `stats/incr/nanmeanvar`'s `docs/repl.txt`, reporting the final example's return value as the "expected" value for every prior step in the chain. Root cause: accumulator-style packages (e.g. `stats/incr/meanvar`) intentionally mutate and return the same output array on every call; the rule captured `actual` values as live references into that shared array, so once the full example had executed, all captured references pointed to the same, already-mutated final state. This commit snapshots each captured value with `@stdlib/utils/copy` at capture time, so earlier steps in a REPL example chain are compared against the value they held when captured rather than the final accumulator state. Ref: https://github.com/stdlib-js/stdlib/actions/runs/29180627893
Follow-up to ad4bf4d per reviewer feedback: `A && B || true` silently swallowed a genuine failure of `apply_patches` itself, not just its absence, hiding real patch-application errors for contributors installing from a git checkout. Switch to a plain `if`/`fi` guard so a present-and-failing script still propagates its exit code, while an absent script (published npm install) still no-ops.
Follow-up to ddb4ab3 per reviewer feedback: wrap the explanatory comment added for the `copy()` snapshot fix to stdlib's usual ~80 column width instead of a single long line.
kgryte
reviewed
Jul 12, 2026
| "check-deps": "make check-deps", | ||
| "check-licenses": "make check-licenses", | ||
| "postinstall": "tools/scripts/apply_patches" | ||
| "postinstall": "if test -f tools/scripts/apply_patches; then tools/scripts/apply_patches; fi" |
Member
There was a problem hiding this comment.
@Planeshifter What is the plan now that the most recent npm version disables running postinstall scripts by default? I suppose we could run the script during make init.
Member
Author
There was a problem hiding this comment.
Hmm, given it's a postinstall script in the project for which one would run npm install, I don't think it will be skipped. The npm change targets scripts from dependencies (packages in node_modules), not the top-level project's own scripts. And we really only need this script for local development.
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.
Description
This pull request fixes two persistent CI failures found on
developin aroutine sweep of the last 24h of GitHub Actions runs. Both landed on one
branch/PR because this session was constrained to a single designated
branch; they are otherwise unrelated fixes and are kept as separate commits.
postinstallfails on every real npm install. The weeklytest_published_packageworkflow has failed 6 consecutive runs (everyrun since 2026-06-07) with
sh: 1: tools/scripts/apply_patches: not found(exit 127).package.json'spostinstallhook runstools/scripts/apply_patchesto patch dev dependencies frometc/patches/, but.npmignorehas excluded/tools/(and/etc/)from the published npm package since 2021 — well before the
postinstallhook was added in December 2025. Anynpm installof@stdlib/stdlibfrom the registry (not a git checkout) fails outright,since the referenced script never exists in the installed tree. Fixed
by guarding the hook so it no-ops when the script is absent, while
still running (and propagating the real exit code of)
apply_patchesfor contributors who install from a git checkout.
Spurious
doctestlint failures ondocs/repl.txt.lint_changed_filesfailed on a new package,
stats/incr/nanmeanvar, reporting 4 "doctest"mismatches that all claimed the expected value was the final example's
result, even for lines documenting earlier intermediate values. The
documented values are mathematically correct (verified independently via
Welford's algorithm, and corroborated by an unrelated open PR, docs: clean-up #13435,
whose reviewers found no fault with
nanmeanvar's docs). Root cause: thedoctestlint rule (@stdlib/_tools/repl-txt/rules/doctest, used forevery package's
docs/repl.txtin CI) captured each REPL example'sreturn value as a live reference (
scope[varName]) rather than asnapshot. Accumulator-style packages such as
stats/incr/meanvarintentionally mutate and return the same output array on every call,
so by the time the rule compared captured values against the documented
text, every captured reference pointed at the same, already-mutated
final state — producing false positives for any chained-call
accumulator example. Fixed by snapshotting each captured value with
@stdlib/utils/copyat capture time.Related Issues
No.
Questions
No.
Other
Failing runs:
postinstall: https://github.com/stdlib-js/stdlib/actions/runs/29173854732doctestrule: https://github.com/stdlib-js/stdlib/actions/runs/29180627893Validation: No local
node_moduleswere available in this environmentto run
make lint-pkg-jsonor the full lint tool end-to-end (ittransitively requires third-party packages, e.g.
debug, via@stdlib/namespace). Instead:postinstall: verifiedpackage.jsonremains valid JSON; directlyexercised the new shell guard under
sh -cin three scenarios — scriptpresent, script absent, and script present-but-failing — confirming exit
0 (no-op) only when absent, and the real exit code otherwise.
doctestrule: verified@stdlib/utils/copyloads standalone andcorrectly deep-clones arrays/primitives/
undefined/nullwithoutmutation leakage; reproduced the exact reference-aliasing failure mode
(and confirmed the fix) in an isolated
vm-based script mirroring therule's example-execution loop; manually recomputed
nanmeanvar'sdocumented values via Welford's algorithm to confirm they were correct
all along.
Three independent reviewers (correctness, regression-scope, style — two
Opus, one Sonnet) evaluated both fixes against the original job logs, root
cause, and diff. All three approved both fixes with no blocking findings.
Reviewer notes:
postinstall's originalA && B || trueform silentlyswallowed a genuine
apply_patchesfailure, not just its absence;switched to
if/fiso the real exit code still propagates when thescript is present.
doctestrule's explanatory comment was reflowed from asingle ~180-char line to stdlib's usual ~80-column wrapping.
copy()in thedoctestrule is appliedunconditionally to every captured value. For the common cases —
primitives, plain arrays/objects, standard typed arrays,
Date,RegExp,Error, complex scalars — this is verified safe. For a fewexotic container types not in
@stdlib/utils/copy's typed-array hash(e.g.
Complex64Array/Complex128Array,BooleanArray,ndarray),cloning falls back to a documented-as-"fragile" generic instance clone.
This could theoretically produce a false doctest failure if some other
package's
docs/repl.txtdocuments a chained accumulator returning oneof those types. Low probability, and not exercisable end-to-end in this
environment (no
node_modules) — worth a maintainer spot-check againsta
docs/repl.txtusing one of those types before merge.Two further CI failures from the same 24h window were investigated and
found to be intermittent (network/timing flakes), not fixed here per the
routine's classification criteria; see the local report for detail.
Checklist
AI Assistance
If you answered "yes" above, how did you use AI assistance?
Disclosure
This PR was authored by Claude Code as part of a scheduled routine that
reviews the last 24h of GitHub Actions failures on
develop, classifiesthem as intermittent or persistent, and proposes fixes for persistent
failures. Root cause for each fix was traced by reading source, git
history, and CI logs, and independently validated with isolated
reproduction scripts (not the full test suite, which could not be run in
this environment). Three independent Claude reviewer passes (correctness,
regression-scope, style) evaluated both fixes before this PR was opened.
@stdlib-js/reviewers
Generated by Claude Code