Skip to content

Bug: Unexpected process exit on next tick when rule throws error since 9.14 #19243

@AriPerkkio

Description

@AriPerkkio

Environment

Node version: v22.12.0
npm version: v10.9.0
Local ESLint version: v9.14.0 (Currently used)
Global ESLint version: Not found
Operating System: darwin 24.1.0

What parser are you using?

Default (Espree)

What did you do?

Configuration
new ESLint({
  overrideConfigFile: true,
  overrideConfig: [
    ...compat.plugins("eslint-plugin-local-rules"),
    { rules: { "local-rules/some-unstable-rule": "error" } },
  ],
  allowInlineConfig: false,
  ignore: true,
  ignorePatterns: ["!**/node_modules/"],
});

// eslint-local-rules.cjs
"use strict";

module.exports = {
  "some-unstable-rule": {
    meta: {
      docs: {
        description: "intentionally crash",
        category: "Possible Errors",
        recommended: false,
      },
      schema: [],
    },
    create: function () {
      return {
        Identifier: function (node) {
          if (node.name === "attributeForCrashing") {
            node.name.attributeForCrashing.someAttribute;
          }
        },
      };
    },
  },
};
// index.mjs
import { ESLint } from "eslint";
import { FlatCompat } from "@eslint/eslintrc";

const compat = new FlatCompat({ baseDirectory: process.cwd() });

const linter = new ESLint({
  overrideConfigFile: true,
  overrideConfig: [
    ...compat.plugins("eslint-plugin-local-rules"),
    { rules: { "local-rules/some-unstable-rule": "error" } },
  ],
});

try {
  await linter.lintFiles("./source.mjs");
  console.log("Lint success");
} catch (error) {
  console.error("Caught error");
}

console.log("First");

await new Promise((r) => setImmediate(r)); // tick tick

console.log("Second");


// source.mjs

// Identifier.name = attributeForCrashing
window.attributeForCrashing();

What did you expect to happen?

On ESLint 9.13.0 it works as expected:

$ node index.mjs 
Caught error
First
Second

Check exit code:

$ echo $?
0

What actually happened?

On ESLint 9.14.0 and above. The linter.lintFiles does something automatically on backgroud, and sets Node to exit on next tick (narrowing down root cause here was quite difficult). This is unexpected. I would expect my script to reach at the end of file and not exit automatically. (If there was automatic exit, I would expect it to happen immediatelly and not after next tick.)

try {
  await linter.lintFiles("./source.mjs");
} catch (error) {
  console.error("Caught error");
}

console.log("First");

await new Promise((r) => setImmediate(r)); // tick tick

console.log("Second"); // This line is never reached
 $ node index.mjs 
Caught error
First
/Users/x/repros/eslint/eslint-local-rules.cjs:17
            node.name.attributeForCrashing.someAttribute;
                                           ^

TypeError: Cannot read properties of undefined (reading 'someAttribute')
Occurred while linting /Users/x/repros/eslint/source.mjs:2
Rule: "local-rules/some-unstable-rule"
    at Identifier (/Users/x/repros/eslint/eslint-local-rules.cjs:17:44)
    at ruleErrorHandler (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/linter.js:1084:48)
    at /Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/node-event-generator.js:297:26)
    at NodeEventGenerator.applySelectors (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/node-event-generator.js:326:22)
    at NodeEventGenerator.enterNode (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/node-event-generator.js:337:14)
    at runRules (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/linter.js:1128:40)
    at #flatVerifyWithoutProcessors (/Users/x/repros/eslint/node_modules/.pnpm/eslint@9.14.0/node_modules/eslint/lib/linter/linter.js:1911:31) {
  ruleId: 'local-rules/some-unstable-rule',
  currentNode: <ref *2> Node {
    type: 'Identifier',
    start: 49,
    end: 69,
    loc: SourceLocation {
      start: Position { line: 2, column: 7 },
      end: Position { line: 2, column: 27 }
    },
    range: [ 49, 69 ],
    name: 'attributeForCrashing',
    parent: <ref *1> Node {
      type: 'MemberExpression',
      start: 42,
      end: 69,
      loc: SourceLocation {
        start: Position { line: 2, column: 0 },
        end: Position { line: 2, column: 27 }
      },
      range: [ 42, 69 ],
      object: Node {
        type: 'Identifier',
        start: 42,
        end: 48,
        loc: SourceLocation {
          start: Position { line: 2, column: 0 },
          end: Position { line: 2, column: 6 }
        },
        range: [ 42, 48 ],
        name: 'window',
        parent: [Circular *1]
      },
      property: [Circular *2],
      computed: false,
      optional: false,
      parent: <ref *3> Node {
        type: 'CallExpression',
        start: 42,
        end: 71,
        loc: SourceLocation {
          start: Position { line: 2, column: 0 },
          end: Position { line: 2, column: 29 }
        },
        range: [ 42, 71 ],
        callee: [Circular *1],
        arguments: [],
        optional: false,
        parent: Node {
          type: 'ExpressionStatement',
          start: 42,
          end: 72,
          loc: SourceLocation { start: [Position], end: [Position] },
          range: [ 42, 72 ],
          expression: [Circular *3],
          parent: Node {
            type: 'Program',
            start: 42,
            end: 72,
            loc: [SourceLocation],
            range: [Array],
            body: [Array],
            sourceType: 'module',
            comments: [Array],
            tokens: [Array],
            parent: null
          }
        }
      }
    }
  }
}

Node.js v22.12.0

Check exit code:

$ echo $?
1

Link to Minimal Reproducible Example

https://stackblitz.com/~/edit/eslint-exit-on-crash-9-14, https://stackblitz.com/~/edit/eslint-exit-on-crash-9-13

Participation

  • I am willing to submit a pull request for this issue.

Additional comments

My use case is here: https://github.com/AriPerkkio/eslint-remote-tester/blob/6cbee8ff67cac7d467e7d8099d3f175d6174ff2c/packages/eslint-remote-tester/src/engine/worker-task.ts#L243-L277

This is run inside node:worker_threads, which now started to unexpectedly crash due to this bug.

Metadata

Metadata

Assignees

Labels

acceptedThere is consensus among the team that this change meets the criteria for inclusionbugESLint is working incorrectlycoreRelates to ESLint's core APIs and featuresrepro:yesIssues with a reproducible example

Type

No type

Projects

Status

Complete

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions