Skip to content

ci: restore EMFILE test on Node.js 26 - #2

Open
anurag6569201 wants to merge 1 commit into
qa/agent-eslint-eslint/pr-02-21297/basefrom
qa/agent-eslint-eslint/pr-02-21297/head
Open

anurag6569201 wants to merge 1 commit into
qa/agent-eslint-eslint/pr-02-21297/basefrom
qa/agent-eslint-eslint/pr-02-21297/head

Conversation

@anurag6569201

Copy link
Copy Markdown

Prerequisites checklist

AI acknowledgment

  • I did not use AI to generate this PR.
  • (If the above is not checked) I have reviewed the AI-generated content before submitting.

What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[X] Other, please explain: Restore the EMFILE check on Node.js 26 by switching generateEmFileError() from readFile() to writeFile().

Fixes eslint#21274.

What changes did you make? (Give an overview)

This PR restores the EMFILE check on Node.js 26 by changing its probe from readFile() to writeFile().

Since Node.js 26.8.0, small path-based readFile() calls perform open, fstat, read, and close in a single thread pool task (nodejs/node#65327). Concurrent reads therefore no longer accumulate file descriptors. As a result, generateEmFileError() stopped raising EMFILE and failed with the error EMFILE error not encountered. eslint#21265 temporarily skipped the check on Node.js 26.

Specifically, this PR:

  • Replaces the readFile() probe with writeFile(). Path-based writes still open and close files in separate thread pool tasks in current Node.js releases, allowing concurrent writes to exhaust file descriptors. This also reflects the --fix workload exercised earlier in the script.
  • Removes the Node.js 26 skip and its TODO from the Test EMFILE Handling step in .github/workflows/ci.yml.

I ran node tools/check-emfile-handling.js, the command behind npm run test:emfile, on macOS with both soft and hard RLIMIT_NOFILE set to 512. I tested Node.js 24.18.0, 26.7.0, and 26.8.1. In all three cases, the ESLint run passed and the probe raised EMFILE.

Is there anything you'd like reviewers to focus on?

Windows behavior

Does writeFile() reliably raise EMFILE on windows-latest, where this CI step also runs? The script notes that Windows has no hard file-descriptor limit, and I could not test it locally.

Probe contents

The probe overwrites each generated file with // Overwritten. The content is arbitrary because the probe runs after ESLint and the directory is removed on exit. I kept it as a valid line comment in case the files are inspected.

Future Node.js behavior

As noted on the issue, nodejs/node#65489 would batch small path-based writeFile() calls into a single thread pool task. If that change ships in a Node.js version covered by ESLint CI, this probe will stop raising EMFILE. The check will then fail with the error EMFILE error not encountered. This PR restores the check for current Node.js releases, but it is not a permanent way to trigger EMFILE.

A raw open() probe would keep triggering EMFILE, but it would no longer demonstrate that ESLint's actual read or write workload can exhaust file descriptors. If maintainers prefer a durable check now, deterministic EMFILE/ENFILE injection is the better direction.

The optimization proposed in nodejs/node#65489 does not cover every promise-based write. Data larger than 512 KiB stays on the existing FileHandle path. Other activity in the process can also exhaust file descriptors. The optimization alone therefore would not establish that the retry handling in ESLint.outputFixes() can be removed.

Disclosure: I'm a participant of open source contribution program OSSCA.

Summary by CodeRabbit

  • Tests
    • Improved reliability of file-descriptor limit testing by validating concurrent file writes.
    • Expanded EMFILE handling checks to run across all supported Node.js versions, including Node.js 26.x.
    • Increased confidence that file-system resource exhaustion is handled consistently across runtime versions.
    • Updated test coverage to better reflect current Node.js behavior when many files are accessed concurrently.

Source merge-base: 87e0a082438264ad90b87fd74165ab4fd90f63ef
Source head: 0fd7ce0732003894d0bb26bc9fb8c35d5d748cb6

@shipwright-agent

Copy link
Copy Markdown

⛔ Shipwright · Blocked

Recommendation: do not merge PR #2 · Tier T3
Checks: 0 total · 0 needing attention

Next step: resolve the blocking findings before merge.

Findings (3)

  • CRITICAL The EMFILE test now overwrites every generated file with '// Overwritten' via concurrent writeFile calls. · tools/check-emfile-handling.js:91
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The comment says 'writing is also what --fix does above', but the test now writes a hardcoded string '// Overwritten' to every file. · tools/check-emfile-handling.js:91
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.
  • HIGH The CI workflow removes the Node 26.x exclusion for the EMFILE test without adding any alternative guard or timeout. · .github/workflows/ci.yml:91
    • Fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

Fireworks usage: 5,958 input · 454 output · 6,412 total tokens · $0.0016 · 9s · 0 fix iteration(s)

Open the Shipwright check for full evidence and the audit bundle. Use /shipwright rerun to verify again.

const fileName = `file_${i}.js`;

return readFile(`${OUTPUT_DIRECTORY}/${fileName}`);
return writeFile(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · CRITICAL

The EMFILE test now overwrites every generated file with '// Overwritten' via concurrent writeFile calls.

Impact: The EMFILE test now overwrites every generated file with '// Overwritten' via concurrent writeFile calls. If the test fails partway or is interrupted, the output directory is left with corrupted files. More importantly, writeFile with a string payload opens the file with 'w' flag, truncating it. On filesystems or platforms where write open does not consume a file descriptor in the same way as read open (e.g., delaye…

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

const fileName = `file_${i}.js`;

return readFile(`${OUTPUT_DIRECTORY}/${fileName}`);
return writeFile(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shipwright · HIGH

The comment says 'writing is also what --fix does above', but the test now writes a hardcoded string '// Overwritten' to every file.

Impact: The comment says 'writing is also what --fix does above', but the test now writes a hardcoded string '// Overwritten' to every file. A future maintainer reading this will not understand why the test writes a comment string instead of simulating the actual fix output, and whether the content matters for triggering EMFILE. The magic string is unexplained and the relationship to --fix is asserted but not demonstrated.

Suggested fix: Review the cited evidence, fix the risk if confirmed, and rerun Shipwright.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant