Skip to content

fix(root): a job that installs nothing is checked for every program it starts - #1864

Open
mobeenabdullah wants to merge 5 commits into
mainfrom
fix/the-metadata-check-imports-only-builtins
Open

mobeenabdullah wants to merge 5 commits into
mainfrom
fix/the-metadata-check-imports-only-builtins

Conversation

@mobeenabdullah

@mobeenabdullah mobeenabdullah commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

The fix for main's red scheduled metadata job is #1879. This pull request is the guard that stops that failure from coming back, together with the shared module reader it now builds on.

Guard, so this cannot recur silently

scripts/no-install-jobs.test.mjs runs in pull-request CI (pnpm test:scripts, in the ci job). It derives, from the workflow files themselves, every program a job starts before it has installed dependencies. It then fails on any module in that program's import graph that Node alone cannot load. Today that population is ci.yml gatescripts/ci-gate.mjs and repo-metadata.yml metadatascripts/check-repo-metadata.mjs.

A guard that reads a workflow differently from GitHub passes over exactly what it exists to catch, so it reads the way GitHub does:

What How
Workflow and action structure js-yaml, the parser github-yaml-parses.test.mjs already applies to these files, so folded, quoted, flow-style and continued run: values are the string GitHub runs
Which steps count In run order, up to the job's first step that installs the repository's dependencies and does nothing else: no if:, no continue-on-error, no package argument, no second command, and run in the repository root once a job or workflow default directory is applied. Only options known to leave a full install count, so --lockfile-only, --dry-run and --prod keep the job checked. An install inside a local action counts only when the step using the action has no condition of its own
Local actions Composite actions expanded in place, with the environment of the step using them. A JavaScript action's pre runs at job start; its main and post are read where its step is, since a post runs only for a step that ran and even after a failed install
Shell New scripts/shell-commands.mjs handles quoting, comments, continuations, operators, redirections, here-documents and command substitution. An expansion is marked non-literal rather than guessed. A shell fed its script through < has that file read
Which shell The step's shell, else the job's or workflow's defaults.run.shell, else the runner's default: bash for an ubuntu- or macos- image or a linux or macOS label, PowerShell for Windows. Only bash, sh, zsh and dash are read, since that is the grammar the tokenizer implements. A shell that is Node refuses the step as inline code, in every spelling a command has: nodejs, any case, a path, .exe. A composite action's step names its own, so a job default does not reach it
node arguments Node's grammar: --, value options that consume the next word (checked against the binary), and = forms. The modules -r/--import/--loader preload are walked too. --env-file is refused, since the file can set NODE_OPTIONS. node.exe counts as Node
Directories working-directory from the step, the job's defaults or the workflow's
Imports @nextlyhq/module-specifiers, the repository's one reader for what a source file loads. It now also reads a require function createRequire returned, whether bound to a name or called where it is made, require.resolve() and import.meta.resolve(), and labels each runtime reference with the resolver that finds it and whether it runs the module. The walk follows only what runs, still reports a package that is only resolved, and follows a relative specifier exactly as written for an import and through Node's documented CommonJS search, package.json main included, for a require

When it cannot settle something, it refuses, naming the step and the reason, rather than passing over it:

  • a node argument that is not literal, an unknown option, or inline code or --run;
  • the word node anywhere else;
  • a package manager running scripts;
  • NODE_OPTIONS;
  • a script under a shell other than bash, sh, zsh or dash, including a Windows runner's default PowerShell and a job whose runs-on names no single system;
  • a cd before node;
  • an action it cannot read, or a directory outside the repository;
  • a program named only at run time, unless it is mapped to the repository file it copies. There is one mapping today, for the review bot's staged review-bot-gh.sh, and a control proves it is load-bearing.

The earlier additions to workflow-run-blocks.mjs are reverted, since this check no longer uses that module.

Verification

Every test was run against a wrong implementation of what it guards: 97 of 97 were caught, each by the test named for it. A sample:

Wrong implementation Caught by
The original defect: the metadata check imports the MDX-compiling module again the real-workflow case for scripts/check-repo-metadata.mjs
An install anywhere in a job exempts the whole job reads the steps before a job's install, and none after it
An install behind if: ends the checked stretch does not end it at an install with a condition
Local actions not expanded five unit cases, and the real changes job control
Folded blocks not folded reads a folded script, and the real ci.yml folded-command control
Every node option treated as a flag the --conditions, -C, preload and refusal cases
working-directory ignored the step, job-default and workflow-default cases
require() ignored reads require, require.resolve, … and follows a relative require …
Substitutions kept raw the tokenizer cases, and finds node in a command substitution …
A job's default shell ignored refuses a script the job's / the workflow's default shell runs as Node code
The condition on a step using an action ignored does not end it at an install inside an action whose step has a condition
An install's directory read from the step alone does not end it at an install in a job's default directory
--env-file accepted one refusal case per spelling
node.exe not recognised starts Windows' node.exe, by name or by path, from bash on a Windows runner
A require function createRequire returned ignored the reader's a require function createRequire returned cases, and the guard's
Every relative specifier resolved as an import follows a relative require through the forms require tries
An option that only writes the lockfile treated as an install the four new install-option rows
A JavaScript action's post read at the job's end reads a JavaScript action's post when the step using it comes before the install
A file only resolved walked as if it ran, or a package only resolved passed does not walk a file require.resolve / import.meta.resolve only finds, with its loading control, and reports a package … looks for
package.json main ignored, or any step of Node's CommonJS search dropped or reordered the file a relative require loads: 20 specifiers on disk, each walked by the guard and resolved by Node's own require.resolve, which agree on Node 20.20.2, 22.18.0, 24.17.0 and 26.8.2
A created require called where it is made ignored the reader's a created require called where it is made, and the guard's
PowerShell, or any shell the tokenizer does not implement, read as bash refuses a Windows runner's default PowerShell … with its bash control, and the named-shell, runner-label and composite cases

test:scripts 0 (43 files, 1543 tests) · @nextlyhq/module-specifiers 72 tests, check-types and lint 0 · the admin, builder and ui suites that consume the reader pass unchanged · lint:scripts 0 · check:comments 0 · changeset gate 0 · fallow pass.

One changeset for the release group, because @nextlyhq/module-specifiers changes.

The fix itself, and the closing of #1799, are in #1879.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: dc64fa0b-a528-4893-accb-92b736e8e57a

📥 Commits

Reviewing files that changed from the base of the PR and between 2510625 and 70f0af3.

⛔ Files ignored due to path filters (2)
  • .changeset/a-created-require-is-a-module-load.md is excluded by !.changeset/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • package.json
  • packages/module-specifiers/README.md
  • packages/module-specifiers/src/index.test.ts
  • packages/module-specifiers/src/index.ts
  • scripts/no-install-jobs.mjs
  • scripts/no-install-jobs.test.mjs
  • scripts/shell-commands.mjs
  • scripts/shell-commands.test.mjs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T20:02:10.593032Z 70f0af3 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ccd074aee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 677 clone groups · ⚠️ 1036 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (677 groups · 28396 lines · 3.9%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:925-967
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 672 more groups.

Across 425 files.

Complexity (1036 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:966 <arrow> critical 246 ! 308 ! 13317.5 ! 1650
collections/services/collection-mutation-service.ts:6366 <arrow> critical 168 ! 155 ! 6264.4 ! 1307
src/init/reload-config.ts:1417 applyReload critical 143 ! 211 ! 4560 ! 1470
shared/lib/entry-validation.ts:245 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
dynamic-collections/services/dynamic-collection-schema-service.ts:1050 generateAlterTableMigration critical 104 ! 221 ! 2440.3 ! 782

5134 files, 80089 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@70f0af3

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@70f0af3

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@70f0af3

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@70f0af3

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@70f0af3

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@70f0af3

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@70f0af3

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@70f0af3

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@70f0af3

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@70f0af3

@nextlyhq/eslint-plugin

npm i https://pkg.pr.new/@nextlyhq/eslint-plugin@70f0af3

nextly

npm i https://pkg.pr.new/nextly@70f0af3

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@70f0af3

@nextlyhq/plugin-mcp

npm i https://pkg.pr.new/@nextlyhq/plugin-mcp@70f0af3

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@70f0af3

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@70f0af3

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@70f0af3

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@70f0af3

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@70f0af3

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@70f0af3

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@70f0af3

commit: 70f0af3

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b864ad3420

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5f72de937

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@mobeenabdullah mobeenabdullah changed the title fix(root): the scheduled metadata check loads nothing an install would provide fix(root): a job that installs nothing is checked for every program it starts Sep 13, 2026
@mobeenabdullah
mobeenabdullah force-pushed the fix/the-metadata-check-imports-only-builtins branch from 7e27138 to 862a123 Compare September 13, 2026 18:15
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 862a123775

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/module-specifiers/src/index.ts Outdated
Comment thread scripts/no-install-jobs.mjs
Comment thread scripts/no-install-jobs.mjs Outdated
Comment thread scripts/no-install-jobs.mjs Outdated
@github-actions github-actions Bot added type: docs Documentation only dependencies Dependency updates (label applied by Dependabot) labels Sep 13, 2026
…d provide

The "About line and topics" job in repo-metadata.yml has failed on every
scheduled run since 2026-09-11, tracked in #1799. The About line and the topics
were never wrong: the job crashes with ERR_MODULE_NOT_FOUND before it examines
either.

That job runs straight after checkout with no install, on purpose.
check-repo-metadata.mjs imported the retired-category patterns from
check-docs-claims.mjs, and #1767 gave that module an `@mdx-js/mdx` import, while
its own import of check-docs-compile.mjs brings `js-yaml` too. ES imports
evaluate eagerly, so importing two regexes loaded an MDX compiler the job has no
way to resolve. The first failure ran an hour after #1767 merged, and nothing on
that pull request could have seen it: the job runs on a schedule only, and
pull-request CI installs dependencies before anything else runs.

The patterns and their classifier move, verbatim, into retired-category.mjs,
which imports nothing. The metadata check imports it directly; the docs-claims
check imports and re-exports it, so there is still exactly one definition and
every existing importer keeps its path.

no-install-jobs.test.mjs holds the property from now on, in pull-request CI. It
derives the jobs that install nothing from the workflow files themselves — today
the CI gate and this check — and walks each script's import graph with
TypeScript's parser, failing on any module Node alone cannot load, and on any
import() it cannot follow rather than passing over it.

Deriving that population exposed a blind spot in workflow-run-blocks.mjs: a step
written `- run: ...` on the list item's own line was invisible to it. No
workflow uses that form today, but a job written that way would have passed the
guard unexamined. It is read now, with a block scalar bounded at the key's
column rather than the dash's, so the step's own `env:` is not swallowed.

Verified by reproducing the job faithfully — the whole scripts/ directory with
no node_modules anywhere above it, under a schedule event — which exits 1 on
main and 0 here; and by running every new test against the wrong
implementation, including the exact original defect, which the guard fails
naming both packages.
… refuses the rest

The guard now reads workflows and local actions with js-yaml, the parser
github-yaml-parses.test.mjs already applies to the same files, so a folded,
quoted, flow-style or continued run: is the string GitHub runs.

- Steps are read in run order and only up to the job's first install that
  can release it: unconditional, in the repository root, doing nothing else.
- Composite actions are expanded in place; a JavaScript action's pre and
  post are placed at the job's start and end.
- Scripts are split into commands by a new shell-commands.mjs, and a node
  command's arguments are read with Node's grammar, so an option's value is
  not taken for the script and a preloaded module is walked too.
- working-directory is resolved from the step, the job or the workflow.
- The module walk follows require, require.resolve, createRequire bindings
  and import.meta.resolve, resolving a relative require as require does.
- Whatever the reader cannot settle is refused with the step and a reason.

The text reader's additions are reverted: nothing uses them now.
…ns, env files and node.exe

- A step's shell is its own, else the job's or the workflow's
  defaults.run.shell, and a shell that runs Node refuses the step. A
  composite action's step keeps the shell it names.
- A condition or continue-on-error on the step using a local action binds
  every step inside it, so an install there no longer ends the checked
  stretch.
- An install ends the stretch only when it runs in the repository root,
  with job and workflow default directories applied.
- --env-file and --env-file-if-exists refuse the command, since the file
  can set NODE_OPTIONS.
- node.exe and nodejs.exe count as Node, by name or by Windows path, and
  when named inside other text.
… and five more gaps close

- The guard's TypeScript walk is replaced by @nextlyhq/module-specifiers,
  now a root devDependency. The reader gains what the guard needed:
  a require function createRequire returned (only when createRequire
  comes from module or node:module), require.resolve and
  import.meta.resolve, and a resolution label on every runtime
  reference, which is how a relative specifier is followed the way Node
  follows it.
- An install ends the checked stretch only with options known to leave a
  full install, so --lockfile-only, --dry-run and --prod keep it checked.
- A JavaScript action's post is read where the step using it is.
- A step's shell is Node in every spelling a command has.
- A step using a local action passes its environment into it.
- The tokenizer keeps < input files, and a shell fed a script that way
  has the script read.
…ader cannot parse

The shared module reader recognises a created require called where it is
made, and labels every runtime reference with whether it runs the module or
only finds it. The guard walks only what runs, still reports a package that
is only resolved, and follows a relative require through Node's documented
CommonJS search, package.json main included, held to require.resolve on disk.

A step's script is read only under bash, sh, zsh or dash. A Windows runner's
default PowerShell, any other named shell, and a runner whose runs-on names
no single system are refused, with the remedy in the reason.
@mobeenabdullah
mobeenabdullah force-pushed the fix/the-metadata-check-imports-only-builtins branch from 862a123 to 70f0af3 Compare September 13, 2026 19:47
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70f0af360c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const INERT_SUBCOMMANDS = new Set([
"install", "i", "ci", "add", "audit", "view", "info", "show", "whoami", "config", "get", "set",
"ls", "list", "why", "outdated", "store", "bin", "root", "prefix", "cache", "dist-tag", "ping",
"help", "enable", "prepare", "use",

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 Badge Treat pnpm prepare as a repository script

When a pre-install step runs pnpm prepare, the root package's prepare script (husky, at package.json:86) executes, but this shared allowlist classifies prepare as inert for every package manager, so no refusal or start is emitted; on a fresh checkout this can fail because husky is not installed. With the pinned pnpm 9.0.0, pnpm help run says “Runs a defined package script,” pnpm help prepare returns “No results,” and an isolated package confirms that pnpm prepare invokes its prepare script; make these subcommands manager-specific or remove prepare for pnpm.

Useful? React with 👍 / 👎.

Comment on lines +300 to +304
ts.isImportDeclaration(node) &&
ts.isStringLiteralLike(node.moduleSpecifier) &&
NODE_MODULE_SPECIFIERS.has(node.moduleSpecifier.text)
) {
recordImportedBindings(node, bindings);

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 Badge Track createRequire obtained from dynamic imports

When a dependency-free entry uses const { createRequire } = await import("node:module") and then calls createRequire(import.meta.url)("js-yaml"), this collector records only the builtin dynamic import; it never marks createRequire as a factory, so nonBuiltinImports returns clean while Node attempts to load the missing package. This loader form is already recognized elsewhere at packages/ui/src/layering.test.ts:898-905; extend the shared reader's binding discovery rather than adding another guard-specific scan.

AGENTS.md reference: AGENTS.md:L299-L302

Useful? React with 👍 / 👎.


/** Node's options that take no value. */
const NODE_FLAGS = new Set([
"-c", "--check", "--enable-source-maps", "--expose-gc", "--frozen-intrinsics", "--no-addons",

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 Badge Do not walk imports in Node check mode

When a no-install step runs node --check scripts/a.mjs and that file imports an npm package, this flag classification causes readNodeCommand to register a normal start and startOffenders to walk its imports, failing the guard even though Node never resolves them. The inspected Node 20.20.2 node --help defines -c, --check as “syntax check script without executing,” and node --check exits 0 for a file importing an absent package; unlike the earlier option-argument gap, this option is recognized but its non-executing semantics are lost. Treat check mode as non-loading while still validating explicit preloads, which Node does execute.

Useful? React with 👍 / 👎.

* What the word after each redirection operator is. `<` names the file a command reads as its
* input, which a shell given no script file runs; every other operator is followed by a file
* written to or duplicated, which runs nothing.
*/

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 Badge Preserve read-write stdin redirections

When a Bash step runs bash <> scripts/run.sh, Bash opens that file on standard input for reading and writing and executes its contents, but this map classifies only < as an input source; the tokenizer drops the <> target as a generic file, so readShellCommand sees no script and Node commands inside it are omitted. Fresh evidence beyond the earlier < fix is that <> remains recognized by REDIRECTION but absent here; preserve the redirected descriptor and treat <> as input when it targets fd 0, including the implicit-fd form.

Useful? React with 👍 / 👎.

defaults,
defaultShell: job.defaults?.run?.shell ?? workflow.defaults?.run?.shell,
runnerShell: runnerDefaultShell(job["runs-on"]),
env: [workflow.env, job.env],

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 Badge Include the job container's environment

When a container job sets jobs.<job_id>.container.env.NODE_OPTIONS, GitHub supplies that variable to every step in the container, but this scope includes only workflow-level and job-level env. A builtin-only node scripts/check.mjs is consequently recorded without its container-provided preload and passes the guard even though Node attempts to load the missing package before the entry; include job.container?.env in the effective environment.

Useful? React with 👍 / 👎.

Comment on lines +526 to +529
for (const specifier of parsed.preloads) {
const path = /^[./]/.test(specifier) ? repositoryPath(specifier, state.cwd, "preload") : null;
if (path !== null && "refusal" in path) return refuse(state, path.refusal);
preloads.push(path === null ? { package: specifier } : { path: path.path });

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 Badge Resolve require preloads with CommonJS rules

When a no-install command uses node -r ./tools/setup scripts/a.js and the repository contains tools/setup.js, Node loads that extension-added file, but this code records the path exactly as written and startOffenders reports tools/setup: cannot be read. The inspected Node 20.20.2 help identifies -r, --require as a “CommonJS module to preload,” and the runtime resolves the extensionless form successfully; preserve the preload option's resolver and reuse the existing CommonJS target resolution instead of treating --require like --import and --loader.

AGENTS.md reference: AGENTS.md:L299-L302

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency updates (label applied by Dependabot) type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant