Repository: eslint/eslint
Pull request #19717: feat: Allow flags to be set in ESLINT_FLAGS env variable
Author: login: nzakas
Labels: name: cli
description: Relates to ESLint's command-line interface
name: core
description: Relates to ESLint's core APIs and features
name: accepted
description: There is consensus among the team that this change meets the criteria for inclusion
name: feature
description: This change adds a new feature to ESLint
Created: 2025-05-09T15:35:43Z | Merged: 2025-05-15T18:16:52Z

<!--
    Thank you for contributing!

    ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md))
[ ] Add autofix to a rule
[ ] Add a CLI option
[x] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/latest/contribute/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

Added the ability to set `ESLINT_FLAGS` environment variable to set flags in the CLI.

fixes #19100

#### Is there anything you'd like reviewers to focus on?

<!-- markdownlint-disable-file MD004 -->


Linked issue #19100: Change Request: allow passing feature flags via environment variable
### ESLint version

v9.x

### What problem do you want to solve?

Currently the only way to pass [feature flags](https://eslint.org/docs/latest/flags/) is via the CLI or NodeJS API directly.

This is cumbersome at scale because:
- opting-in users into the flags can require large-scale coordination to get people to change their IDE config
- turning on a flag means having to update all NodeJS API usages across a repository at the same time

### What do you think is the correct solution?

A much simpler alternative would be allowing passing of feature flags via an environment variable.
This would allow setting of the flags via direnv or similar tools to transparently turn on the flags for all developers and tools working in the repo.

For example at Canva we have tools that would allow us to inject an `export ESLINT_FLAGS="unstable_config_lookup_from_file"` or similar into all users `.bashrc`/etc file -- but for various reasons we could not easily get all users to change their IDE configs (there are multiple IDEs and no shared configuration, for example).

### Participation

- [ ] I am willing to submit a pull request for this change.

### Additional comments

_No response_
Linked issue comments: user: login: snitin315
created_at: 2024-11-05T15:00:19Z
body: I'm 👍🏻 for this. This seems to be a valid use case and shouldn't be complicated to implement. I'd like to know what other team members think about this change.

// @eslint/eslint-team 
author_association: CONTRIBUTOR
user: login: nzakas
created_at: 2024-11-05T17:12:46Z
body: I like it. We should put together an RFC for this, as I think this is something that needs a bit of thought before implementing:

* Name of the environment variable
* How does setting the environment variable interact with CLI flags? (Override them completely, merge them together, etc.)
* Given that flags are temporary, does this mean we should change the behavior of what happens when you pass an inactive flag (currently errors)? I'm thinking of a situation where a flag goes inactive and so everyone's ESLint at a company breaks at the same time until someone updates the environment variable again.

author_association: MEMBER
user: login: snitin315
created_at: 2024-11-05T17:38:12Z
body: I'll prepare an RFC.
author_association: CONTRIBUTOR
user: login: nzakas
created_at: 2025-01-14T20:44:13Z
body: @snitin315 are you still working on this?
author_association: MEMBER
user: login: nzakas
created_at: 2025-05-09T14:59:47Z
body: We actually did a bunch of the heavy lifting in https://github.com/eslint/eslint/pull/19386.

I'll finish this up for implementing the environment variable piece.
author_association: MEMBER

Diff:
diff --git a/docs/src/pages/flags.md b/docs/src/pages/flags.md
index 4d12207180af..0d873fac6bf5 100644
--- a/docs/src/pages/flags.md
+++ b/docs/src/pages/flags.md
@@ -82,6 +82,16 @@ On the command line, you can specify feature flags using the `--flag` option. Yo
     args: ["--flag", "flag_one", "--flag", "flag_two", "file.js"]
 }) }}
 
+### Enable Feature Flags with Environment Variables
+
+You can also set feature flags using the `ESLINT_FLAGS` environment variable. Multiple flags can be specified as a comma-separated list and are merged with any flags passed on the CLI or in the API. For example, here's how you can add feature flags to your `.bashrc` or `.bash_profile` files:
+
+```bash
+export ESLINT_FLAGS="flag_one,flag_two"
+```
+
+This approach is especially useful in CI/CD pipelines or when you want to enable the same flags across multiple ESLint commands.
+
 ### Enable Feature Flags with the API
 
 When using the API, you can pass a `flags` array to both the `ESLint` and `Linter` classes:
@@ -98,6 +108,10 @@ const linter = new Linter({
 });
 ```
 
+::: tip
+The `ESLint` class also reads the `ESLINT_FLAGS` environment variable to set flags.
+:::
+
 ### Enable Feature Flags in VS Code
 
 To enable flags in the VS Code ESLint Extension for the editor, specify the flags you'd like in the `eslint.options` setting in your `settings.json` file:
diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js
index a9203d44a52d..e02f6f4d23fe 100644
--- a/lib/eslint/eslint.js
+++ b/lib/eslint/eslint.js
@@ -389,6 +389,20 @@ function getFixerForFixTypes(fix, fixTypesSet, config) {
 		originalFix(message);
 }
 
+/**
+ * Retrieves flags from the environment variable ESLINT_FLAGS.
+ * @param {string[]} flags The flags defined via the API.
+ * @returns {string[]} The merged flags to use.
+ */
+function mergeEnvironmentFlags(flags) {
+	if (!process.env.ESLINT_FLAGS) {
+		return flags;
+	}
+
+	const envFlags = process.env.ESLINT_FLAGS.trim().split(/\s*,\s*/gu);
+	return Array.from(new Set([...envFlags, ...flags]));
+}
+
 //-----------------------------------------------------------------------------
 // Main API
 //-----------------------------------------------------------------------------
@@ -419,7 +433,7 @@ class ESLint {
 		const linter = new Linter({
 			cwd: processedOptions.cwd,
 			configType: "flat",
-			flags: processedOptions.flags,
+			flags: mergeEnvironmentFlags(processedOptions.flags),
 		});
 
 		const cacheFilePath = getCacheFile(
diff --git a/lib/shared/flags.js b/lib/shared/flags.js
index ae0e3fb81963..526cf910afad 100644
--- a/lib/shared/flags.js
+++ b/lib/shared/flags.js
@@ -27,6 +27,7 @@
  */
 const activeFlags = new Map([
 	["test_only", "Used only for testing."],
+	["test_only_2", "Used only for testing."],
 	[
 		"unstable_config_lookup_from_file",
 		"Look up `eslint.config.js` from the file being linted.",
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index fd4d5bcc25d5..d560402a0d36 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -2741,6 +2741,11 @@ describe("cli", () => {
 						.returns();
 				});
 
+				afterEach(() => {
+					sinon.restore();
+					delete process.env.ESLINT_FLAGS;
+				});
+
 				it("should throw an error when an inactive flag whose feature has been abandoned is used", async () => {
 					const configPath = getFixturePath("eslint.config.js");
 					const filePath = getFixturePath("passing.js");
@@ -2751,6 +2756,18 @@ describe("cli", () => {
 					}, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u);
 				});
 
+				it("should throw an error when an inactive flag whose feature has been abandoned is used in an environment variable", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+
+					process.env.ESLINT_FLAGS = "test_only_abandoned";
+					const input = `--config ${configPath} ${filePath}`;
+
+					await stdAssert.rejects(async () => {
+						await cli.execute(input, null, true);
+					}, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u);
+				});
+
 				it("should error out when an unknown flag is used", async () => {
 					const configPath = getFixturePath("eslint.config.js");
 					const filePath = getFixturePath("passing.js");
@@ -2761,6 +2778,18 @@ describe("cli", () => {
 					}, /Unknown flag 'test_only_oldx'\./u);
 				});
 
+				it("should error out when an unknown flag is used in an environment variable", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+					const input = `--config ${configPath} ${filePath}`;
+
+					process.env.ESLINT_FLAGS = "test_only_oldx";
+
+					await stdAssert.rejects(async () => {
+						await cli.execute(input, null, true);
+					}, /Unknown flag 'test_only_oldx'\./u);
+				});
+
 				it("should emit a warning and not error out when an inactive flag that has been replaced by another flag is used", async () => {
 					const configPath = getFixturePath("eslint.config.js");
 					const filePath = getFixturePath("passing.js");
@@ -2780,6 +2809,28 @@ describe("cli", () => {
 					assert.strictEqual(exitCode, 0);
 				});
 
+				it("should emit a warning and not error out when an inactive flag that has been replaced by another flag is used in an environment variable", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+					const input = `--config ${configPath} ${filePath}`;
+
+					process.env.ESLINT_FLAGS = "test_only_replaced";
+
+					const exitCode = await cli.execute(input, null, true);
+
+					assert.strictEqual(
+						processStub.callCount,
+						1,
+						"calls `process.emitWarning()` for flags once",
+					);
+					assert.deepStrictEqual(processStub.getCall(0).args, [
+						"The flag 'test_only_replaced' is inactive: This flag has been renamed 'test_only' to reflect its stabilization. Please use 'test_only' instead.",
+						"ESLintInactiveFlag_test_only_replaced",
+					]);
+					sinon.assert.notCalled(log.error);
+					assert.strictEqual(exitCode, 0);
+				});
+
 				it("should emit a warning and not error out when an inactive flag whose feature is enabled by default is used", async () => {
 					const configPath = getFixturePath("eslint.config.js");
 					const filePath = getFixturePath("passing.js");
@@ -2799,6 +2850,27 @@ describe("cli", () => {
 					assert.strictEqual(exitCode, 0);
 				});
 
+				it("should emit a warning and not error out when an inactive flag whose feature is enabled by default is used in an environment variable", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+					const input = `--config ${configPath} ${filePath}`;
+
+					process.env.ESLINT_FLAGS = "test_only_enabled_by_default";
+
+					const exitCode = await cli.execute(input, null, true);
+					assert.strictEqual(
+						processStub.callCount,
+						1,
+						"calls `process.emitWarning()` for flags once",
+					);
+					assert.deepStrictEqual(processStub.getCall(0).args, [
+						"The flag 'test_only_enabled_by_default' is inactive: This feature is now enabled by default.",
+						"ESLintInactiveFlag_test_only_enabled_by_default",
+					]);
+					sinon.assert.notCalled(log.error);
+					assert.strictEqual(exitCode, 0);
+				});
+
 				it("should not error when a valid flag is used", async () => {
 					const configPath = getFixturePath("eslint.config.js");
 					const filePath = getFixturePath("passing.js");
@@ -2808,6 +2880,31 @@ describe("cli", () => {
 					sinon.assert.notCalled(log.error);
 					assert.strictEqual(exitCode, 0);
 				});
+
+				it("should not error when a valid flag is used in an environment variable", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+					const input = `--config ${configPath} ${filePath}`;
+
+					process.env.ESLINT_FLAGS = "test_only";
+
+					const exitCode = await cli.execute(input, null, true);
+
+					sinon.assert.notCalled(log.error);
+					assert.strictEqual(exitCode, 0);
+				});
+
+				it("should error when a valid flag is used in an environment variable with an abandoned flag", async () => {
+					const configPath = getFixturePath("eslint.config.js");
+					const filePath = getFixturePath("passing.js");
+					const input = `--config ${configPath} ${filePath}`;
+
+					process.env.ESLINT_FLAGS = "test_only,test_only_abandoned";
+
+					await stdAssert.rejects(async () => {
+						await cli.execute(input, null, true);
+					}, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u);
+				});
 			});
 
 			describe("--report-unused-inline-configs option", () => {
diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js
index 9844e3f7c771..36a3dc99e430 100644
--- a/tests/lib/eslint/eslint.js
+++ b/tests/lib/eslint/eslint.js
@@ -428,6 +428,10 @@ describe("ESLint", () => {
 					.returns();
 			});
 
+			afterEach(() => {
+				delete process.env.ESLINT_FLAGS;
+			});
+
 			it("should return true if the flag is present and active", () => {
 				eslint = new ESLint({
 					cwd: getFixturePath(),
@@ -437,6 +441,47 @@ describe("ESLint", () => {
 				assert.strictEqual(eslint.hasFlag("test_only"), true);
 			});
 
+			it("should return true if the flag is present and active with ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "test_only";
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+				});
+				assert.strictEqual(eslint.hasFlag("test_only"), true);
+			});
+
+			it("should merge flags passed through API with flags passed through ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "test_only";
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+					flags: ["test_only_2"],
+				});
+				assert.strictEqual(eslint.hasFlag("test_only"), true);
+				assert.strictEqual(eslint.hasFlag("test_only_2"), true);
+			});
+
+			it("should return true for multiple flags in ESLINT_FLAGS if the flag is present and active and one is duplicated in the API", () => {
+				process.env.ESLINT_FLAGS = "test_only,test_only_2";
+
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+					flags: ["test_only"], // intentional duplication
+				});
+
+				assert.strictEqual(eslint.hasFlag("test_only"), true);
+				assert.strictEqual(eslint.hasFlag("test_only_2"), true);
+			});
+
+			it("should return true for multiple flags in ESLINT_FLAGS if the flag is present and active and there is leading and trailing white space", () => {
+				process.env.ESLINT_FLAGS = " test_only, test_only_2 ";
+
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+				});
+
+				assert.strictEqual(eslint.hasFlag("test_only"), true);
+				assert.strictEqual(eslint.hasFlag("test_only_2"), true);
+			});
+
 			it("should return true for the replacement flag if an inactive flag that has been replaced is used", () => {
 				eslint = new ESLint({
 					cwd: getFixturePath(),
@@ -485,6 +530,15 @@ describe("ESLint", () => {
 				}, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned/u);
 			});
 
+			it("should throw an error if an inactive flag whose feature has been abandoned is used in ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "test_only_abandoned";
+				assert.throws(() => {
+					eslint = new ESLint({
+						cwd: getFixturePath(),
+					});
+				}, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned/u);
+			});
+
 			it("should throw an error if the flag is unknown", () => {
 				assert.throws(() => {
 					eslint = new ESLint({
@@ -494,6 +548,15 @@ describe("ESLint", () => {
 				}, /Unknown flag 'foo_bar'/u);
 			});
 
+			it("should throw an error if the flag is unknown in ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "foo_bar";
+				assert.throws(() => {
+					eslint = new ESLint({
+						cwd: getFixturePath(),
+					});
+				}, /Unknown flag 'foo_bar'/u);
+			});
+
 			it("should return false if the flag is not present", () => {
 				eslint = new ESLint({ cwd: getFixturePath() });
 


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces the ability to set feature flags via the ESLINT_FLAGS environment variable in the CLI core.  
- Added tests in tests/lib/cli.js to cover various behaviors when using ESLINT_FLAGS, including error and warning cases.  
- Updated lib/cli.js to merge environment flags with any flags passed via the command line using de-duplication.  
- Enhanced documentation in docs/src/pages/flags.md outlining how to use ESLINT_FLAGS.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

| File                     | Description                                                        |
| ------------------------ | ------------------------------------------------------------------ |
| tests/lib/cli.js         | Added unit tests covering flag behavior when set via environment   |
| lib/cli.js               | Modified option translation logic to include and de-duplicate ESLINT_FLAGS  |
| docs/src/pages/flags.md  | Updated documentation with details on ESLINT_FLAGS usage             |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T15:37:20Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-10T17:43:20Z
user: login: nzakas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-12T14:51:47Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds support for setting ESLint feature flags via the `ESLINT_FLAGS` environment variable, merging them with flags passed in the API or CLI.

- Introduces `mergeEnvironmentFlags` to combine `ESLINT_FLAGS` with programmatic flags.
- Adds tests in both the ESLint module and CLI suite to cover error, warning, and success cases when using `ESLINT_FLAGS`.
- Updates documentation to explain how to enable feature flags via `ESLINT_FLAGS`.

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

<details>
<summary>Show a summary per file</summary>

| File                             | Description                                                |
| -------------------------------- | ---------------------------------------------------------- |
| tests/lib/eslint/eslint.js       | Added cleanup hook and tests for `ESLINT_FLAGS` behavior  |
| tests/lib/cli.js                 | Added tests for CLI error/warning scenarios using env var  |
| lib/shared/flags.js              | Registered a new test-only flag (`test_only_2`)            |
| lib/eslint/eslint.js             | Implemented `mergeEnvironmentFlags` and integrated it      |
| docs/src/pages/flags.md          | Documented `ESLINT_FLAGS` usage and examples               |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**tests/lib/eslint/eslint.js:444**
* Add a test case to ensure that whitespace around commas (e.g. `"flag1, flag2"`) is correctly trimmed by the split regex.
```
it("should return true if the flag is present and active with ESLINT_FLAGS", () => {
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:24:06Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T17:52:25Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T18:25:57Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T18:39:50Z
user: login: fasttime
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T05:07:09Z
user: login: nzakas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:15:24Z
user: login: mdjermanovic
body: LGTM, thanks! Leaving open for @fasttime to verify.
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-14T15:51:27Z
user: login: fasttime
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T09:49:30Z
user: login: fasttime
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T10:04:18Z
user: login: mdjermanovic
body: LGTM, thanks! Leaving open for @fasttime to verify.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-15T16:27:41Z
user: login: fasttime
body: LGTM, thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-15T18:16:39Z

Review comments:
diff_hunk: @@ -279,6 +279,17 @@ async function translateOptions(
 		options.warnIgnored = warnIgnored;
 		options.flags = flag;
 
+		/*
+		 * If there are any flags set in the environment, prepend them to the list of flags
+		 * passed in via the command line. This allows users to set flags in their environment
+		 * and have them apply to all ESLint runs.
+		 */
+		if (process.env.ESLINT_FLAGS) {
+			const envFlags = process.env.ESLINT_FLAGS.split(/\s*,\s*/gu);
+			const cliFlags = options.flags || [];
+			options.flags = Array.from(new Set([...envFlags, ...cliFlags]));
+		}
+
path: lib/cli.js
user: login: mdjermanovic
body: Since the environment variable is used only in the ESLint CLI, does this solve the use case from https://github.com/eslint/eslint/issues/19100? The original post of https://github.com/eslint/eslint/issues/19100 mentions IDEs, which typically don't use ESLint CLI.
created_at: 2025-05-10T17:36:17Z
author_association: MEMBER
diff_hunk: @@ -279,6 +279,17 @@ async function translateOptions(
 		options.warnIgnored = warnIgnored;
 		options.flags = flag;
 
+		/*
+		 * If there are any flags set in the environment, prepend them to the list of flags
+		 * passed in via the command line. This allows users to set flags in their environment
+		 * and have them apply to all ESLint runs.
+		 */
+		if (process.env.ESLINT_FLAGS) {
+			const envFlags = process.env.ESLINT_FLAGS.split(/\s*,\s*/gu);
+			const cliFlags = options.flags || [];
+			options.flags = Array.from(new Set([...envFlags, ...cliFlags]));
+		}
+
path: lib/cli.js
user: login: nzakas
body: Ah, I missed that. I guess it would need to be in `ESLint` then.
created_at: 2025-05-12T14:51:47Z
author_association: MEMBER
diff_hunk: @@ -82,6 +82,16 @@ On the command line, you can specify feature flags using the `--flag` option. Yo
     args: ["--flag", "flag_one", "--flag", "flag_two", "file.js"]
 }) }}
 
+### Enable Feature Flags with Environment Variables
+
+You can also set feature flags using the `ESLINT_FLAGS` environment variable. Multiple flags can be specified as a comma-separated list. For example, here's how you can add feature flags to your `.bashrc` or `.base_profile` files:
path: docs/src/pages/flags.md
user: login: Copilot
body: Clarify that environment flags are *merged* with CLI/API flags rather than replacing them, and note the order of precedence (env first, then explicit flags).
created_at: 2025-05-13T15:24:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -428,6 +428,10 @@ describe("ESLint", () => {
 					.returns();
 			});
 
+			afterEach(() => {
+				delete process.env.ESLINT_FLAGS;
+			});
path: tests/lib/eslint/eslint.js
user: login: Copilot
body: [nitpick] The `afterEach` hook for deleting `process.env.ESLINT_FLAGS` appears in multiple describe blocks; consider moving it to a higher-level common hook to DRY up the tests.
```suggestion
			// Removed redundant afterEach hook for deleting process.env.ESLINT_FLAGS
```
created_at: 2025-05-13T15:24:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2741,6 +2741,11 @@ describe("cli", () => {
 						.returns();
 				});
 
+				afterEach(() => {
+					sinon.restore();
+					delete process.env.ESLINT_FLAGS;
path: tests/lib/cli.js
user: login: Copilot
body: [nitpick] This cleanup hook is duplicated for the CLI tests as well; you might consolidate environment-variable teardown across both ESLint and CLI suites.
```suggestion
					resetEnvironmentVariables();
```
created_at: 2025-05-13T15:24:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,6 +82,16 @@ On the command line, you can specify feature flags using the `--flag` option. Yo
     args: ["--flag", "flag_one", "--flag", "flag_two", "file.js"]
 }) }}
 
+### Enable Feature Flags with Environment Variables
+
+You can also set feature flags using the `ESLINT_FLAGS` environment variable. Multiple flags can be specified as a comma-separated list. For example, here's how you can add feature flags to your `.bashrc` or `.base_profile` files:
path: docs/src/pages/flags.md
user: login: mdjermanovic
body: ```suggestion
You can also set feature flags using the `ESLINT_FLAGS` environment variable. Multiple flags can be specified as a comma-separated list. For example, here's how you can add feature flags to your `.bashrc` or `.bash_profile` files:
```

I think the filename is `.bash_profile`.
created_at: 2025-05-13T17:49:55Z
author_association: MEMBER
diff_hunk: @@ -437,6 +441,27 @@ describe("ESLint", () => {
 				assert.strictEqual(eslint.hasFlag("test_only"), true);
 			});
 
+			it("should return true if the flag is present and active with ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "test_only";
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+					flags: ["test_only"],
path: tests/lib/eslint/eslint.js
user: login: mdjermanovic
body: ```suggestion
```

I believe this particular test would be more representative if the same flag isn't passed through options.
created_at: 2025-05-13T18:23:24Z
author_association: MEMBER
diff_hunk: @@ -437,6 +441,27 @@ describe("ESLint", () => {
 				assert.strictEqual(eslint.hasFlag("test_only"), true);
 			});
 
+			it("should return true if the flag is present and active with ESLINT_FLAGS", () => {
+				process.env.ESLINT_FLAGS = "test_only";
+				eslint = new ESLint({
+					cwd: getFixturePath(),
+					flags: ["test_only"],
+				});
+				assert.strictEqual(eslint.hasFlag("test_only"), true);
+			});
path: tests/lib/eslint/eslint.js
user: login: mdjermanovic
body: ```suggestion
			});
			
			it("should merge flags passed through API with flags passed through ESLINT_FLAGS", () => {
				process.env.ESLINT_FLAGS = "test_only";
				eslint = new ESLint({
					cwd: getFixturePath(),
					flags: ["test_only_2"],
				});
				assert.strictEqual(eslint.hasFlag("test_only"), true);
				assert.strictEqual(eslint.hasFlag("test_only_2"), true);
			});
```

It would be good to ensure the flags are merged. Currently, if we remove `...flags` from the merge function, no tests would fail.
created_at: 2025-05-13T18:37:56Z
author_association: MEMBER
diff_hunk: @@ -98,6 +108,10 @@ const linter = new Linter({
 });
 ```
 
+::: tip
+The `ESLint` class also reads the `ESLINT_FLAGS` environment variable to set flags.
+:::
path: docs/src/pages/flags.md
user: login: fasttime
body: If a tool needs to create an `ESLint` instance without a flag that is set in the environment variable, what will it do? I believe the only way to instantiate the `ESLint` class with a predictable set of flags will be modifying the current process environment.

```js
delete process.env.ESLINT_FLAGS;
const eslint = new ESLint({ flags: myFlagsNotTheirs });
```

Are we fine with this usage?
created_at: 2025-05-14T05:07:09Z
author_association: MEMBER
diff_hunk: @@ -98,6 +108,10 @@ const linter = new Linter({
 });
 ```
 
+::: tip
+The `ESLint` class also reads the `ESLINT_FLAGS` environment variable to set flags.
+:::
path: docs/src/pages/flags.md
user: login: nzakas
body: I think that's okay as it seems like an edge case to me.
created_at: 2025-05-14T14:15:24Z
author_association: MEMBER
diff_hunk: @@ -98,6 +108,10 @@ const linter = new Linter({
 });
 ```
 
+::: tip
+The `ESLint` class also reads the `ESLINT_FLAGS` environment variable to set flags.
+:::
path: docs/src/pages/flags.md
user: login: fasttime
body: Okay, then let's use the merging approach for the constructor option and the environment variable.
created_at: 2025-05-15T09:49:30Z
author_association: MEMBER
diff_hunk: @@ -389,6 +389,20 @@ function getFixerForFixTypes(fix, fixTypesSet, config) {
 		originalFix(message);
 }
 
+/**
+ * Retrieves flags from the environment variable ESLINT_FLAGS.
+ * @param {string[]} flags The flags defined via the API.
+ * @returns {string[]} The merged flags to use.
+ */
+function mergeEnvironmentFlags(flags) {
+	if (!process.env.ESLINT_FLAGS) {
+		return flags;
+	}
+
+	const envFlags = process.env.ESLINT_FLAGS.split(/\s*,\s*/gu);
path: lib/eslint/eslint.js
user: login: fasttime
body: Since we are removing space characters between a flag and a comma I think it makes sense to also remove leading and trailing spaces from the environment variable for consistency.
created_at: 2025-05-15T10:04:18Z
author_association: MEMBER

PR comments:
user: login: netlify[bot]
created_at: 2025-05-09T15:35:48Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *docs-eslint* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 9f1319672e555667489b6c80b1e918ab11829f0d |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/projects/docs-eslint/deploys/68260aeb13b40c00087cc6c3 |
author_association: NONE
user: login: nzakas
created_at: 2025-05-13T15:21:06Z
body: Moved reading environment variable to `ESLint` constructor.
author_association: MEMBER
user: login: fasttime
created_at: 2025-05-15T10:23:12Z
body: Just left a suggestion, otherwise LGTM.
author_association: MEMBER