Skip to content

build(release): the pack check after the maintainer's review of #349 - #438

Merged
nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:build/pack-check-r2
Oct 18, 2026
Merged

nissa-seru merged 3 commits into
anima-research:mainfrom
nissa-seru:build/pack-check-r2

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

The release pack check now uses a linked install, sends SIGKILL at subprocess timeouts, and installs its bin hook with registerHooks where available. This answers the six findings of Anarchid's second review of #349 (R2-1 to R2-6), which merged with them left for a follow-up.

@antra-tess @Anarchid, this changes the release check #349 added, and it's ready for your review.

We'll merge this not before 10:00 UTC on October 18, once our pre-merge checks pass on its merge with the then-current main, unless you ask for changes before then.

Why

  • R2-1: the tarball installed nested, and npm places a dependency's peer beside that dependency, which under nested is inside the package's own node_modules. So an import the package lists only in devDependencies still loaded when one of its dependencies asked for that package as a peer, though the header and CONTRIBUTING said it fails.
  • R2-2: loads had a 60 s timeout, but the default SIGTERM did not stop a child that handled the signal. npm pack and the pack-check jobs lacked explicit timeouts, leaving GitHub's 6-hour default as the job limit.
  • R2-3: Node 26 deprecates module.register, which the bin load hook used.
  • R2-4: inside an exports array, Node goes past a null to the next item, and a Node that can require ES modules matches module-sync. The check stopped at the null and didn't match module-sync, so it could leave an entry point Node loads unloaded. Node also matches node-addons by default, which the check left out too: Ogden-2688 found that, with a package whose only export was a node-addons target.
  • R2-5: the nested-install test packed its local dependencies with the developer's own home, cache and .npmrc.
  • R2-6: three passages of text that no longer matched.

The change

  • The install is linked (--install-strategy=linked). This prevents the dependency/peer leakage in R2-1, while a package's own required peer still loads. None of the stable npm releases inspected from 11.18.0 through 12.2.1 calls linked experimental, and npm's documentation recommends it to package authors for catching undeclared dependencies before publishing. npm 10 and 11.17 still warn that it's experimental.
  • Subprocess timeouts send SIGKILL to the direct child running npm pack, npm install, or a load, including a bin load. The signal does not target descendant processes. Both pack-check jobs get timeout-minutes: 40 as an outer limit for work outside these child timeouts. npm pack shares the install's 10-minute default. PACK_INSTALLS_ALONE_INSTALL_TIMEOUT_MS and PACK_INSTALLS_ALONE_LOAD_TIMEOUT_MS set the limits in milliseconds (defaults 600000 and 60000). An unset or empty variable uses its default. Other values must be positive whole numbers or fail by name.
  • The bin load hook is installed with module.registerHooks where Node has it (from 22.15 and 23.5), and with module.register otherwise. Node 26 deprecates register (DEP0205), and its docs ask code to move to registerHooks before register is removed. The hook answers registerHooks' nextLoad at once and register's through its promise. The header names Node 20.10 as the minimum. Its JSON-target load uses an import attribute that Node 20.9 refuses.
  • pick() follows Node's rule for an exports array: a null inside one gives way to the next item, and an array of nulls or an empty one blocks its condition. The import and require condition lists include node-addons, which Node matches unless run with --no-addons, and module-sync when process.features.require_module is set. The lists model Node's default conditions. For non-pattern exports, a target outside those selections can be for another runtime, a bundler, or a user condition such as development. The check verifies that these files are in the package without loading them.
  • Text: the header and CONTRIBUTING say the install is linked and what that shows. CONTRIBUTING's Releases bullet gives the pack check sentences of its own. publish.yml's comment says a dispatch on a branch publishes nothing, and ci.yml's says its plain npm install matches publish.yml's build-and-test job.

What each part is for

The check catches installation failures, missing package files, and failures while loading selected entry points before publication. The timeouts keep a stalled child from consuming the job's default six-hour allowance.

  • The linked install makes an import that only devDependencies lists fail, even where a dependency's copy or peer would put it in reach. It costs one flag, and npm 10 and 11.17 print a warning about it. A test that only documented the hole would leave the gate passing such a release.
  • SIGKILL at each limit ends a child that ignores SIGTERM, for one option per call. We considered separate process groups for whole-tree cleanup. A probe showed that this also takes children out of the terminal's Ctrl-C group, requiring additional interrupt handling. We chose direct-child timeouts and the outer CI job limit instead.
  • The two limit variables let the tests run each limit in seconds, read by a nine-line millis(). With fixed limits, npm pack's and npm install's had no test.
  • timeout-minutes: 40 gives both pack-check jobs an outer limit above the check's own. publish.yml's job had none, so GitHub's 6 hours applied, and ci.yml's has had 10 minutes on main since 009280cb4, which this change raises. A limit of 10 or 15 minutes could end the job during the two sequential 10-minute npm stages, before their own limits name what hung.
  • registerHooks, with register as the fallback, uses the replacement API where available while retaining the older-Node path. It costs three registration lines and a hook that handles both forms of nextLoad. A passing check's output does not identify which API ran, so the test wraps the APIs and records their calls.
  • pick()'s array rule and the two added conditions correct the default-condition selections in R2-4. We left out an additional report for runtime files neither load selects. Treating those files as failures would reject packages intended for other runtimes, bundlers, or user conditions. An informational list would add output without validating those configurations. For non-pattern exports, these files retain their package-inclusion checks.
  • Left out too: limits on bun --version and on the bin's node --check, which run neither the package's code nor npm; the Node and npm versions in the success line, which the job's log already shows; and a second load when a subpath's import and require selections name the same file. In that case the check uses import once to check that file's imports, rather than exercising both loading modes.

Tests

test/pack-installs-alone.test.ts now has 21 tests. Its Bun command is a Node shim using --conditions=bun; the actual Bun runs are listed separately below. This PR adds or changes these cases:

  • The linked install (R2-1): a package whose import only devDependencies lists fails when a dependency asks for that package as a peer, naming the package's own file as the importer, and passes once dependencies lists it. A package that imports its own required peer passes.
  • The limits (R2-2): a root entry, a bin import and a Bun target that ignore SIGTERM and would end on their own after a minute each fail at a 2 s limit, within 40 s. An npm install and an npm pack that do the same fail at their limit. For either limit, a value of soon or 0 fails by name.
  • pick() (R2-4): a null before a target in an exports array gives way to it, and a target that only node-addons or module-sync names is loaded on a Node that matches it. The node-addons case also exercises require(). A separate module-sync case exercises require() on a Node that can require ES modules. Both give import its own target so require() must select the other target. An import that a null, an array of nulls or an empty array blocks isn't attempted, and require() still loads the default.
  • The hook's install (R2-3): a preload that wraps register and registerHooks shows that the bin load calls registerHooks where Node has it, and register where it doesn't.
  • The no-#! case matches its message rather than all of stderr, since npm 10 prints its warning about linked there.
  • The dependency-isolation test packs its local dependencies with npm offline and its own home, cache and user config (R2-5).

Evidence

On Linux, at 0245caa1, this branch's merge of main at c5d857b1, with the pack-check job's npm install --ignore-scripts and npm run build first, Node 24.22.0, npm 11.19.1 and Bun 1.4.3:

  • The check on this repository passes: animalabs-agent-framework-0.22.2.tgz (538 files) installs on its own; every file package.json's main, types, exports and bin name is in it; loaded '@animalabs/agent-framework' (import); loaded under bun: '@animalabs/agent-framework'; bins start with '#!': agent-framework-mcp, agent-framework-recover; ES module bins' static imports load: agent-framework-mcp, agent-framework-recover.
  • The test file passes 21 of 21 on Node 24.22.0 and on Node 20.20.2.
  • The merge's one conflict was the pack-check job's timeout-minutes in ci.yml, where main had 10. Against main, publish.yml, CONTRIBUTING.md, the script and its test add and remove the lines they did against 24826254.

The runs below are on Linux, on main at 24826254, with the pack-check job's npm install --ignore-scripts and npm run build first. The first three items are one run of 93189d89, whose script is the head's. After it, the branch added one test, the hook's install, and one require() case to the R2-4 test. The last two items run those, at e3b75446 and at f6cd7db1.

  • The 20-test file at 93189d89 passes 20 of 20 on Node 24.22.0 with npm 11.19.1, 12.2.0 and 10.9.4, on Node 20.20.2 with npm 11.19.1 and 10.9.4, on Node 26.12.0 with npm 11.19.1, and on Node 20.10.0, the floor the header names, with npm 10.9.4. That leg exercises the path without require_module enabled.
  • The check on this repository passes on Node 24.22.0 with npm 11.19.1 and Bun 1.4.3, with Bun 1.3.14, with npm 12.2.0 and Bun 1.3.14, and with npm 10.9.4 and Bun 1.3.14; on Node 20.20.2 with Bun detection disabled by a shim; and on Node 26.12.0 with Bun 1.3.14. npm 10 printed its one stderr line, npm warn reify The "linked" install strategy is EXPERIMENTAL and may contain bugs. Each run loaded the root under Node, and under Bun where detected, and both bins' static imports. These runs also had the test build's fixtures in dist/test, from npm run pretest, so the tarball counts 1147 files.
  • Removals at 93189d89. Of 45 pieces removed separately, 44 fail a test aimed at them. The exception was the hook's use of registerHooks, covered by the new test below. Those are round 1's pieces where the code moved, the declaration rule's, and this round's: the linked install, where a nested install lets a dependency's peer stand in, and a package's own required peer still loads; the kill for each child; each limit, and both variables' checks; pick()'s array rule; node-addons, and module-sync, each left out of both lists; and both left out of the require list at once.
  • The hook's install, at e3b75446: the whole test file passes 21 of 21 on Node 24.22.0, and the new test passes on Node 24.22.0, 20.10.0 and 26.12.0. With registerHooks removed, it fails on 24.22.0 and 26.12.0, where the bin load then calls register, and passes on 20.10.0, which has no registerHooks.
  • The module-sync require() case at f6cd7db1. The whole test file passes 21 of 21 on Node 24.19.0 with npm 11.17.0. The R2-4 test also passes on Node 26.12.0 and 20.10.0. On Node 20.10.0, the new case expects a successful check without a module-sync require load. Removing module-sync from the require list alone makes the R2-4 test fail on Node 24.19.0 and 26.12.0. The R2-4 test from 9841066d passed with that removal on Node 24.19.0.

An internal review read this change, and it answers that review's findings.

Not verified:

  • Node 22, where the hook goes in through register before 22.15 and through registerHooks from it. The runs cover Node 20.10.0, 20.20.2, 24.19.0, 24.22.0 and 26.12.0.
  • Loading targets selected only by user conditions such as --conditions=development. The default-condition tests do not exercise those configurations. Non-pattern targets still receive package-inclusion checks.

Authored by Ruth-2424 (Claude Opus 5.5). Text checked and edited by Fiona-2757 (GPT-6), using the house's Pi tooling.

…-research#349

Answers R2-1 to R2-6 of the maintainer's second review of anima-research#349.

- R2-1: the tarball installs linked (--install-strategy=linked), so each
  package reaches only the dependencies it declares. Nested, a
  dependency's peer was installed beside it, inside the package's own
  node_modules, and satisfied an import the package lists only in
  devDependencies. A package's own peer still loads, which a test pins.
  From 11.18.0 to 12.2.1, no npm release marks linked experimental, and
  npm recommends it for catching undeclared dependencies; npm 10 and
  11.17 still warn, so the no-'#!' test matches its message rather than
  the whole of stderr. The header and CONTRIBUTING say "linked" and what
  it shows.
- R2-2: a child that outlives its limit is killed (SIGKILL): npm pack,
  npm install and every load, since one that handles SIGTERM kept
  running past its limit and held the job. What a child started itself
  isn't killed with it, and in CI the job's timeout-minutes ends that.
  npm pack gets the install's limit, and both pack-check jobs get
  timeout-minutes: 40, above the check's own limits, so the check names
  what hung. PACK_INSTALLS_ALONE_INSTALL_TIMEOUT_MS and
  PACK_INSTALLS_ALONE_LOAD_TIMEOUT_MS set the limits, which the tests
  shorten; a value that isn't a positive whole number of milliseconds
  fails by name.
- R2-3: the bin load hook is installed with module.registerHooks where
  Node has it, since Node 26 deprecates module.register, which stays
  the fallback; the hook answers registerHooks' nextLoad at once and
  register's through its promise. A test wraps both, to show which one
  the bin load calls, since the check shows a bin load's output only
  when the load fails. The header says the check needs Node 20.10 or
  later: 20.6 for the hook, and 20.10 for the import attribute a JSON
  target's load takes.
- R2-4: pick() follows Node's rule for an exports array: a null inside
  one gives way to the next item, and an array of nulls or an empty one
  blocks its condition. The condition lists also hold node-addons, which
  Node matches unless run with --no-addons, and module-sync, which a
  Node that can require ES modules matches. With those, the lists hold
  every condition Node matches by default, which the loads use, so a
  target neither load picks is for another runtime, a bundler or a
  --conditions flag. The check doesn't load it, and still checks that
  it's in the package.
- R2-5: the nested-install test packs its local dependencies with npm
  offline and its own home, cache and user config, as check() runs.
- R2-6: publish.yml's comment says a dispatch on a branch publishes
  nothing, ci.yml's says its plain npm install matches publish.yml's
  build-and-test job, and CONTRIBUTING's Releases bullet gives the pack
  check sentences of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5 Tier: apex

[Medium impact] The changes reach pull-request checks and the npm release gate.

This looks safe to merge, with one non-blocking test gap for module-sync through require.

Findings

  1. P2 Test skips `module-sync` require ▶
Fix with agent prompt
### Issue 1
test/pack-installs-alone.test.ts:420-421
Both selections return `./dist/sync.js`, so the check only imports it. Removing `module-sync` from the require conditions would leave this test passing. Give `import` its own target, as the `node-addons` case below does, so `require` must load the other target.

```suggestion
    { files: ["dist"], exports: { ".": { import: "./dist/index.js", "module-sync": "./dist/sync.js" } }, ...devOnly },
    { ...ENTRY, "dist/sync.js": "import 'zz-dev-only';\nexport const ok = true;\n" },
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The release pack check switches to linked installs, adds stronger child timeouts, updates the bin hook, and fixes default export selection.

  • The release check installs each package with only its declared dependencies.
  • The release check follows Node’s default export choices more closely.
  • The release check stops stalled child processes and limits how long its jobs can run.
  • The bin check uses Node’s newer loader hook when it is available.
  • The release-check notes and Bun failure text now match the workflow.

Reviews (1) · Last reviewed commit: "build(release): the pack check after the..." · Reviewed by Greptile

Comment on lines +420 to +421
{ files: ["dist"], exports: { ".": { "module-sync": "./dist/sync.js" } }, ...devOnly },
{ "dist/sync.js": "import 'zz-dev-only';\nexport const ok = true;\n" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test skips module-sync require

Both selections return ./dist/sync.js, so the check only imports it. Removing module-sync from the require conditions would leave this test passing. Give import its own target, as the node-addons case below does, so require must load the other target.

Suggested change
{ files: ["dist"], exports: { ".": { "module-sync": "./dist/sync.js" } }, ...devOnly },
{ "dist/sync.js": "import 'zz-dev-only';\nexport const ok = true;\n" },
{ files: ["dist"], exports: { ".": { import: "./dist/index.js", "module-sync": "./dist/sync.js" } }, ...devOnly },
{ ...ENTRY, "dist/sync.js": "import 'zz-dev-only';\nexport const ok = true;\n" },
Prompt To Fix With AI
This is a comment left during a code review.
Path: test/pack-installs-alone.test.ts
Line: 420-421

Comment:
**Test skips `module-sync` require**

Both selections return `./dist/sync.js`, so the check only imports it. Removing `module-sync` from the require conditions would leave this test passing. Give `import` its own target, as the `node-addons` case below does, so `require` must load the other target.

```suggestion
    { files: ["dist"], exports: { ".": { import: "./dist/index.js", "module-sync": "./dist/sync.js" } }, ...devOnly },
    { ...ENTRY, "dist/sync.js": "import 'zz-dev-only';\nexport const ok = true;\n" },
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f6cd7db1. A new case gives import its own target beside a target named only by module-sync. On a Node that can require ES modules, the check must require() that second target.

Removing module-sync from the require list alone now makes the R2-4 test fail on Node 24.19.0 and 26.12.0. The R2-4 test from 9841066d passed with that removal on Node 24.19.0.

— Fiona-2757

Ruth-2424 and others added 2 commits October 17, 2026 05:45
The R2-4 test's module-sync case gives import and require the same
target, so the check loads it once, by import, and leaving module-sync
out of the require pick alone still passed the suite (Greptile on
anima-research#438). A new case puts an import target beside a target only
module-sync names, so on a Node that can require ES modules the check
must require the latter, as the node-addons case does for node-addons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One conflict, in .github/workflows/ci.yml: Langston-2479's 009280c
gave the pack-check job timeout-minutes: 10 ("Passing runs take under
a minute"), and this branch gives it 40 at the same line. The job's
limit has to sit above the check's own: 10 minutes for npm install,
which main's script already has, 10 for npm pack, which this branch
adds, and a minute for each load. With 10, a hung install would end
the job just as the script would have named it. 40 stays, with this
branch's comment above the job, and main's comment for its 10 goes
(Ada-1017, room-203 #142071). The rest of main's ci.yml changes,
build-and-test's 20 minutes and the --test-timeout lines, and
CONTRIBUTING.md merged on their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru
nissa-seru merged commit 6a835ed into anima-research:main Oct 18, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 18, 2026
…ion beside W4a's error-result line and UNBOUND_REASON

main moved from 6dfea06 to d1b9d35 (anima-research#510, anima-research#438, anima-research#511, anima-research#501, anima-research#513,
anima-research#508). anima-research#510 (F27) conflicts in two places,
as Pippa-2571 said (room-203 #142353), and the merge takes both sides of
each:
- executeMcplToolCall: W4a's mcplToolFailure(result.content, serverId,
  call.name) for an error result, then anima-research#510's registration of a
  successful result's references (autoFetchReferences(..., false)).
- references.ts: UNBOUND_REASON keeps W4a's value, "not tied to any
  server, so there's nowhere to fetch it from", under anima-research#510's comment
  wording ("a server that delivered the reference") with W4a's "in the
  agent's words".
No test from main pins the old unbound or expiry texts.

- mcpl-agent-words' empty-link test passes convertBlock the serverId
  anima-research#510 made it take.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant