Current Behavior
When FORCE_COLOR=0 is set in the environment (common in CI to suppress ANSI codes), Nx deletes it in bin/nx.ts and then re-introduces FORCE_COLOR=true for every forked child task. This causes all child processes (ESLint, TypeScript, Vitest, etc.) to emit ANSI escape codes even though the user explicitly requested no colors.
Root Cause
The workaround for picocolors#100 introduced in PR #34520 deletes FORCE_COLOR from the parent process, but task-env.ts defaults undefined to 'true':
Step 1 — bin/nx.ts (lines 4-8):
if (process.env.FORCE_COLOR === '0') {
process.env.NO_COLOR = '1';
delete process.env.FORCE_COLOR; // FORCE_COLOR is now undefined
}
Step 2 — task-env.ts (line 19) and task-orchestrator.ts (lines 681, 1000):
process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR
// Since FORCE_COLOR was deleted → evaluates to 'true'
Step 3 — task-env.ts (line 76):
FORCE_COLOR: forceColor, // 'true' is injected into every child's env
The child process receives both FORCE_COLOR=true and NO_COLOR=1, but per the force-color spec and Node.js docs, FORCE_COLOR takes precedence over NO_COLOR.
Expected Behavior
FORCE_COLOR=0 should propagate to child tasks, resulting in no ANSI escape codes in their output.
Impact
This affects any CI environment that sets FORCE_COLOR=0 to get parseable plain-text output. In our case, ANSI escape codes in ESLint output broke downstream log parsers that use regex to extract lint errors (the ✖ N problems (M errors, O warnings) summary line contains invisible ANSI bytes that prevent matching).
Suggested Fix
Preserve the user's original FORCE_COLOR intent before deleting it, and consult the saved value when building child environments:
// bin/nx.ts — save intent before deleting
if (process.env.FORCE_COLOR === '0') {
process.env.NX_ORIGINAL_FORCE_COLOR = '0';
process.env.NO_COLOR = '1';
delete process.env.FORCE_COLOR;
}
// task-env.ts / task-orchestrator.ts — check saved value
process.env.FORCE_COLOR === undefined
? (process.env.NX_ORIGINAL_FORCE_COLOR === '0' ? '0' : 'true')
: process.env.FORCE_COLOR
Workaround
Setting FORCE_COLOR=false (string) instead of FORCE_COLOR=0 bypasses the === '0' check in bin/nx.ts, so the value passes through to children unchanged. However, 'false' is not a standard FORCE_COLOR value per either force-color.org or the chalk/supports-color convention.
Environment
🤖 This issue was drafted with the help of AI
Current Behavior
When
FORCE_COLOR=0is set in the environment (common in CI to suppress ANSI codes), Nx deletes it inbin/nx.tsand then re-introducesFORCE_COLOR=truefor every forked child task. This causes all child processes (ESLint, TypeScript, Vitest, etc.) to emit ANSI escape codes even though the user explicitly requested no colors.Root Cause
The workaround for picocolors#100 introduced in PR #34520 deletes
FORCE_COLORfrom the parent process, buttask-env.tsdefaultsundefinedto'true':Step 1 —
bin/nx.ts(lines 4-8):Step 2 —
task-env.ts(line 19) andtask-orchestrator.ts(lines 681, 1000):Step 3 —
task-env.ts(line 76):The child process receives both
FORCE_COLOR=trueandNO_COLOR=1, but per the force-color spec and Node.js docs,FORCE_COLORtakes precedence overNO_COLOR.Expected Behavior
FORCE_COLOR=0should propagate to child tasks, resulting in no ANSI escape codes in their output.Impact
This affects any CI environment that sets
FORCE_COLOR=0to get parseable plain-text output. In our case, ANSI escape codes in ESLint output broke downstream log parsers that use regex to extract lint errors (the✖ N problems (M errors, O warnings)summary line contains invisible ANSI bytes that prevent matching).Suggested Fix
Preserve the user's original
FORCE_COLORintent before deleting it, and consult the saved value when building child environments:Workaround
Setting
FORCE_COLOR=false(string) instead ofFORCE_COLOR=0bypasses the=== '0'check inbin/nx.ts, so the value passes through to children unchanged. However,'false'is not a standardFORCE_COLORvalue per either force-color.org or the chalk/supports-color convention.Environment
🤖 This issue was drafted with the help of AI