Skip to content

feat(cli): add --no-color flag and fix color propagation to workers#1163

Open
hztBUAA wants to merge 2 commits intoyamadashy:mainfrom
hztBUAA:feat/solarized-disable-colors
Open

feat(cli): add --no-color flag and fix color propagation to workers#1163
hztBUAA wants to merge 2 commits intoyamadashy:mainfrom
hztBUAA:feat/solarized-disable-colors

Conversation

@hztBUAA
Copy link
Copy Markdown

@hztBUAA hztBUAA commented Feb 20, 2026

Summary

Fixes #1057

  • Register --no-color as a proper Commander CLI option so it appears in --help and is not rejected as an unknown option
  • Fix FORCE_COLOR propagation in worker processes: previously workers always set FORCE_COLOR which overrides NO_COLOR. Now respects NO_COLOR env var and --no-color flag by propagating NO_COLOR=1 to child processes instead
  • Fix hardcoded colors: true in logger's formatArgs: now uses pc.isColorSupported so util.inspect respects the user's color settings
  • Add semantic suggestions (plain, monochrome) mapping to --no-color for discoverability
  • Add color property to CliOptions type for Commander compatibility

The underlying color library (picocolors) already natively disables coloring when --no-color is in process.argv or NO_COLOR is set — this PR makes the flag discoverable in --help, fixes bugs where the color-disable setting was not properly propagated, and follows the NO_COLOR standard.

Test plan

  • All 1109 existing tests pass
  • New test: --no-color flag accepted without error (cliRun.test.ts)
  • New test: NO_COLOR env propagated to worker processes (processConcurrency.test.ts)
  • New test: --no-color argv propagated to worker processes (processConcurrency.test.ts)
  • New test: FORCE_COLOR set when colors are not disabled (processConcurrency.test.ts)
  • New test: pc.isColorSupported used in logger formatArgs (logger.test.ts)

Open with Devin

Register --no-color as a proper Commander CLI option so it appears in
--help output and is not rejected as unknown. The underlying picocolors
library already reads process.argv for --no-color and NO_COLOR env var,
but Commander needs to know about the flag.

Additionally:
- Fix FORCE_COLOR propagation in worker processes: previously workers
  always set FORCE_COLOR which overrides NO_COLOR. Now respects NO_COLOR
  env var and --no-color flag by propagating NO_COLOR instead.
- Fix hardcoded colors:true in logger formatArgs: now uses
  pc.isColorSupported so util.inspect respects color settings.
- Add semantic suggestions (plain, monochrome) for --no-color.
- Add color property to CliOptions type.

Fixes yamadashy#1057
@hztBUAA hztBUAA requested a review from yamadashy as a code owner February 20, 2026 04:57
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 20, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds support for disabling colored output through a new --no-color CLI option and implements runtime color detection. Changes include adding the CLI option, updating logger formatting to check terminal color support dynamically, and propagating color environment variables to worker processes.

Changes

Cohort / File(s) Summary
CLI Option and Types
src/cli/cliRun.ts, src/cli/types.ts
Introduces --no-color option with semantic aliases (plain, monochrome), adds color?: boolean property to CliOptions interface for Commander compatibility and worker process propagation.
Logger Color Support
src/shared/logger.ts
Updates formatArgs to determine object inspection colors at runtime via pc.isColorSupported instead of always enabling colors.
Worker Process Environment
src/shared/processConcurrency.ts
Refactors environment propagation to conditionally set NO_COLOR or FORCE_COLOR based on NO_COLOR presence or --no-color flag, enabling color settings to propagate to child processes.
Test Coverage
tests/cli/cliRun.test.ts, tests/shared/logger.test.ts, tests/shared/processConcurrency.test.ts
Adds test cases for --no-color mode, color support in object formatting, and color environment variable propagation to worker processes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: adding a --no-color flag and fixing color propagation to workers, directly reflecting the primary objectives of the PR.
Description check ✅ Passed The description provides comprehensive details about all changes, includes test verification, references the fixed issue, and contains more substantial content than the minimal template requires.
Linked Issues check ✅ Passed The PR fully addresses issue #1057 by implementing a --no-color option to disable coloring as requested, fixing color propagation, and following the NO_COLOR standard.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing --no-color support and fixing color propagation: CLI option registration, type definitions, logger formatting, worker environment setup, and corresponding tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @hztBUAA, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the command-line interface by introducing a "--no-color" flag and improving how color settings are managed across the application, especially within worker processes. The changes ensure that color preferences, whether set via the CLI flag or the NO_COLOR environment variable, are consistently applied throughout the system, including in logging and child processes, aligning with the NO_COLOR standard.

Highlights

  • CLI Option: Registered "--no-color" as a proper Commander CLI option, making it discoverable in "--help" and preventing rejection as an unknown option.
  • Color Propagation: Fixed FORCE_COLOR propagation in worker processes to correctly respect the NO_COLOR environment variable and the new "--no-color" flag, ensuring color-disable settings are propagated.
  • Logger Color Handling: Updated the logger's formatArgs to use pc.isColorSupported instead of a hardcoded colors: true, allowing util.inspect to respect the user's color settings.
  • Semantic Suggestions: Added "plain" and "monochrome" as semantic suggestions that map to the "--no-color" flag for improved discoverability.
  • Type Definition: Included a "color" property in the CliOptions type for Commander compatibility and to facilitate propagating color settings.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/cli/cliRun.ts
    • Added "--no-color" option to the CLI program.
    • Included "plain" and "monochrome" in semanticSuggestionMap to suggest "--no-color".
  • src/cli/types.ts
    • Added an optional "color" property to the CliOptions interface.
  • src/shared/logger.ts
    • Modified formatArgs to dynamically determine color support using pc.isColorSupported for util.inspect.
  • src/shared/processConcurrency.ts
    • Updated createWorkerPool to conditionally set NO_COLOR or FORCE_COLOR environment variables for child processes based on process.env.NO_COLOR or process.argv.includes('--no-color').
  • tests/cli/cliRun.test.ts
    • Added a test case to verify that the "--no-color" flag is accepted without errors.
  • tests/shared/logger.test.ts
    • Mocked picocolors.isColorSupported to be true.
    • Added a test to ensure pc.isColorSupported is passed to util.inspect for object formatting in the logger.
  • tests/shared/processConcurrency.test.ts
    • Removed FORCE_COLOR from expected environment variables in a test.
    • Added tests to verify NO_COLOR propagation to workers when the environment variable is set.
    • Added tests to verify NO_COLOR propagation to workers when "--no-color" is in argv.
    • Added a test to confirm FORCE_COLOR is set in worker environments when colors are not explicitly disabled.
Activity
  • All 1109 existing tests passed.
  • A new test was added to confirm the "--no-color" flag is accepted without error.
  • A new test was added to verify NO_COLOR environment variable propagation to worker processes.
  • A new test was added to verify "--no-color" argument propagation to worker processes.
  • A new test was added to ensure FORCE_COLOR is set when colors are not disabled.
  • A new test was added to confirm pc.isColorSupported is used in the logger's formatArgs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +99 to +101
...(process.env.NO_COLOR || process.argv.includes('--no-color')
? { NO_COLOR: '1' }
: { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Truthy check on process.env.NO_COLOR fails for empty-string values per NO_COLOR standard

The condition at src/shared/processConcurrency.ts:99 uses process.env.NO_COLOR which is a truthy check. Per the NO_COLOR standard, setting NO_COLOR= (empty string) is a valid way to disable colors. However, in JavaScript, '' is falsy, so process.env.NO_COLOR evaluates to false when NO_COLOR is set to an empty string.

Root Cause and Impact

Picocolors itself correctly uses "NO_COLOR" in env (an existence/in check) to detect the variable, which handles empty strings. But the new propagation logic in processConcurrency.ts uses process.env.NO_COLOR (a truthy check):

...(process.env.NO_COLOR || process.argv.includes('--no-color')
  ? { NO_COLOR: '1' }
  : { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),

When a user sets NO_COLOR= (empty string):

  • Parent process: picocolors correctly disables colors (uses "NO_COLOR" in env)
  • Worker child process: the condition evaluates to falsy, so FORCE_COLOR is set instead of NO_COLOR, and colors remain enabled in workers

This creates an inconsistency where the parent process respects NO_COLOR= but worker processes do not, violating the NO_COLOR standard that this PR explicitly aims to support.

Suggested change
...(process.env.NO_COLOR || process.argv.includes('--no-color')
? { NO_COLOR: '1' }
: { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
...('NO_COLOR' in process.env || process.argv.includes('--no-color')
? { NO_COLOR: '1' }
: { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request does a great job of adding the --no-color flag and fixing color propagation to worker processes. The changes are logical and the inclusion of new tests to cover these changes is excellent. I have a few suggestions to improve the design by reducing coupling and to enhance the robustness of the tests. Overall, this is a solid contribution.

Comment on lines +99 to +101
...(process.env.NO_COLOR || process.argv.includes('--no-color')
? { NO_COLOR: '1' }
: { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

While this works, checking process.argv.includes('--no-color') directly creates a coupling between this shared module and a specific CLI flag string. This can be brittle if the flag name changes. A more robust approach would be to pass the color preference down from the CLI command handler. The color property was already added to CliOptions and could be used for this. You could extend WorkerOptions to include the color setting and pass it through initTaskRunner to createWorkerPool. This would decouple this module from the specific CLI implementation details and make the data flow more explicit.

Comment on lines +115 to +122
describe('color support in formatArgs', () => {
it('should pass pc.isColorSupported to util.inspect for object formatting', () => {
const obj = { key: 'value' };
logger.info('Test:', obj);
// When isColorSupported is true (mock), util.inspect should use colors
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('CYAN:Test: '));
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This test is a good start, but it only indirectly verifies the behavior by checking the final console.log output. To make the test more robust and specific, you could spy on util.inspect and assert that it's called with the correct colors option. This would directly test the logic in formatArgs. You could also test the case where colors are disabled to ensure util.inspect is called with colors: false.

Comment on lines +170 to +242
describe('color propagation to worker processes', () => {
beforeEach(() => {
vi.mocked(os).availableParallelism = vi.fn().mockReturnValue(4);
vi.mocked(Tinypool).mockImplementation(function (this: unknown) {
(this as Record<string, unknown>).run = vi.fn();
(this as Record<string, unknown>).destroy = vi.fn();
return this as Tinypool;
});
});

it('should propagate NO_COLOR to worker when NO_COLOR env is set', () => {
const originalNoColor = process.env.NO_COLOR;
process.env.NO_COLOR = '1';
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
NO_COLOR: '1',
}),
}),
);
} finally {
if (originalNoColor === undefined) {
delete process.env.NO_COLOR;
} else {
process.env.NO_COLOR = originalNoColor;
}
}
});

it('should propagate NO_COLOR to worker when --no-color is in argv', () => {
const originalArgv = process.argv;
process.argv = [...originalArgv, '--no-color'];
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
NO_COLOR: '1',
}),
}),
);
} finally {
process.argv = originalArgv;
}
});

it('should set FORCE_COLOR when colors are not disabled', () => {
const originalNoColor = process.env.NO_COLOR;
delete process.env.NO_COLOR;
const originalArgv = process.argv;
process.argv = originalArgv.filter((arg) => arg !== '--no-color');
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
FORCE_COLOR: expect.any(String),
}),
}),
);
} finally {
if (originalNoColor !== undefined) {
process.env.NO_COLOR = originalNoColor;
}
process.argv = originalArgv;
}
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To improve test isolation and reduce repetitive code, consider managing the global state (process.env.NO_COLOR and process.argv) using beforeEach and afterEach hooks for this describe block. You can save the original values in beforeEach and restore them in afterEach. This would make the tests cleaner and less prone to state leakage between tests, as you wouldn't need the try...finally blocks in each test.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/processConcurrency.ts (1)

89-105: ⚠️ Potential issue | 🟡 Minor

Environment variables may not be in optimal order when propagating color settings to child processes.

When --no-color is passed and the parent environment contains FORCE_COLOR, both variables end up in the child process env: { FORCE_COLOR: '1', NO_COLOR: '1', ... }.

Currently, picocolors v1.1.1 checks NO_COLOR first in its detection logic (!(!!env.NO_COLOR || ...)), so NO_COLOR takes absolute precedence and colors are correctly disabled. However, this relies on picocolors' internal evaluation order. Other color libraries may check FORCE_COLOR first, or picocolors may change its detection algorithm in future versions.

To make the behavior unambiguous and library-agnostic, explicitly exclude FORCE_COLOR from the child env when NO_COLOR is set:

Suggested approach
-      env: {
-        ...process.env,
-        REPOMIX_WORKER_TYPE: workerType,
-        REPOMIX_LOG_LEVEL: logger.getLogLevel().toString(),
-        ...(process.env.NO_COLOR || process.argv.includes('--no-color')
-          ? { NO_COLOR: '1' }
-          : { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
-        TERM: process.env.TERM || 'xterm-256color',
-      },
+      env: (() => {
+        const noColor = !!(process.env.NO_COLOR || process.argv.includes('--no-color'));
+        const { FORCE_COLOR: _fc, NO_COLOR: _nc, ...restEnv } = process.env;
+        return {
+          ...restEnv,
+          REPOMIX_WORKER_TYPE: workerType,
+          REPOMIX_LOG_LEVEL: logger.getLogLevel().toString(),
+          ...(noColor ? { NO_COLOR: '1' } : { FORCE_COLOR: process.env.FORCE_COLOR || (process.stdout.isTTY ? '1' : '0') }),
+          TERM: process.env.TERM || 'xterm-256color',
+        };
+      })(),

This destructures the parent env to exclude FORCE_COLOR and NO_COLOR upfront, then sets only the intended value, ensuring no ambiguity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/processConcurrency.ts` around lines 89 - 105, The child_process
env construction can end up containing both NO_COLOR and FORCE_COLOR which
creates ambiguity; update the runtime === 'child_process' env logic so that when
NO_COLOR is present (process.env.NO_COLOR or
process.argv.includes('--no-color')) you explicitly remove/omit FORCE_COLOR from
the child env and only set NO_COLOR='1', otherwise restore FORCE_COLOR based on
process.env.FORCE_COLOR or process.stdout.isTTY; implement this by destructuring
process.env to exclude FORCE_COLOR (and NO_COLOR when setting it) before
spreading into env inside the env object used in the runtime === 'child_process'
block so that functions like logger.getLogLevel(), and the checks
process.env.NO_COLOR / process.argv.includes('--no-color') remain intact but
FORCE_COLOR is never propagated when NO_COLOR is set.
🧹 Nitpick comments (2)
tests/shared/processConcurrency.test.ts (1)

171-178: beforeEach setup is duplicated across initWorker, initTaskRunner, and color propagation blocks.

The three-line os.availableParallelism + Tinypool mock setup is repeated identically in lines 83–91, 135–143, and 171–178. Consider hoisting it to a shared beforeEach at the top-level processConcurrency describe (or a helper function) to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/shared/processConcurrency.test.ts` around lines 171 - 178, The
three-line mock setup for os.availableParallelism and Tinypool that appears in
beforeEach blocks (mocking os.availableParallelism and mocking Tinypool
implementation with run/destroy) is duplicated; hoist this setup into a single
top-level beforeEach inside the processConcurrency describe (or extract into a
helper function called from each beforeEach), replacing the repeated blocks in
the initWorker, initTaskRunner, and color propagation describe blocks so tests
reuse the same mocked os.availableParallelism and Tinypool implementation (refer
to the mocked os.availableParallelism and Tinypool
mockImplementation/run/destroy symbols to locate and remove the duplicates).
tests/shared/logger.test.ts (1)

115-122: Test is a near-duplicate and doesn't validate its stated behavior.

The assertion expect.stringContaining('CYAN:Test: ') is functionally equivalent to the already-existing test at lines 104–108 ('Object:''Test:'). Neither test actually verifies that util.inspect receives { colors: true } — only that the outer pc.cyan() wrapper is applied, which was true before this PR.

To genuinely verify the new colors: useColors path, spy on util.inspect directly:

🧪 Suggested stronger test
+import util from 'node:util';

 describe('color support in formatArgs', () => {
   it('should pass pc.isColorSupported to util.inspect for object formatting', () => {
+    const inspectSpy = vi.spyOn(util, 'inspect');
     const obj = { key: 'value' };
     logger.info('Test:', obj);
-    // When isColorSupported is true (mock), util.inspect should use colors
-    expect(console.log).toHaveBeenCalledWith(expect.stringContaining('CYAN:Test: '));
+    expect(inspectSpy).toHaveBeenCalledWith(obj, expect.objectContaining({ colors: true }));
   });
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/shared/logger.test.ts` around lines 115 - 122, The test is a
near-duplicate and doesn't actually assert that util.inspect receives colors:
true; replace or extend the spec that references formatArgs/logger.info to spyOn
(or jest.spyOn) util.inspect, call logger.info('Test:', obj) with
pc.isColorSupported mocked true, and assert util.inspect was called with the
object and an options object containing { colors: true } (and similarly add a
case for pc.isColorSupported false asserting colors: false); ensure the spy is
restored after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/shared/processConcurrency.test.ts`:
- Around line 170-242: Add a test in processConcurrency.test.ts that ensures
when NO_COLOR is active (either via process.env.NO_COLOR = '1' or via
'--no-color' in process.argv) any pre-existing FORCE_COLOR in the parent env is
not passed to workers: set process.env.FORCE_COLOR = '1' before calling
createWorkerPool, set NO_COLOR (or argv) so the code should drop FORCE_COLOR,
call createWorkerPool, and assert Tinypool was called with env not containing
FORCE_COLOR (or containing NO_COLOR and not FORCE_COLOR). Use the existing test
patterns and restore process.env and process.argv afterward; reference
createWorkerPool and Tinypool to locate where to add the assertion.

---

Outside diff comments:
In `@src/shared/processConcurrency.ts`:
- Around line 89-105: The child_process env construction can end up containing
both NO_COLOR and FORCE_COLOR which creates ambiguity; update the runtime ===
'child_process' env logic so that when NO_COLOR is present (process.env.NO_COLOR
or process.argv.includes('--no-color')) you explicitly remove/omit FORCE_COLOR
from the child env and only set NO_COLOR='1', otherwise restore FORCE_COLOR
based on process.env.FORCE_COLOR or process.stdout.isTTY; implement this by
destructuring process.env to exclude FORCE_COLOR (and NO_COLOR when setting it)
before spreading into env inside the env object used in the runtime ===
'child_process' block so that functions like logger.getLogLevel(), and the
checks process.env.NO_COLOR / process.argv.includes('--no-color') remain intact
but FORCE_COLOR is never propagated when NO_COLOR is set.

---

Nitpick comments:
In `@tests/shared/logger.test.ts`:
- Around line 115-122: The test is a near-duplicate and doesn't actually assert
that util.inspect receives colors: true; replace or extend the spec that
references formatArgs/logger.info to spyOn (or jest.spyOn) util.inspect, call
logger.info('Test:', obj) with pc.isColorSupported mocked true, and assert
util.inspect was called with the object and an options object containing {
colors: true } (and similarly add a case for pc.isColorSupported false asserting
colors: false); ensure the spy is restored after the test.

In `@tests/shared/processConcurrency.test.ts`:
- Around line 171-178: The three-line mock setup for os.availableParallelism and
Tinypool that appears in beforeEach blocks (mocking os.availableParallelism and
mocking Tinypool implementation with run/destroy) is duplicated; hoist this
setup into a single top-level beforeEach inside the processConcurrency describe
(or extract into a helper function called from each beforeEach), replacing the
repeated blocks in the initWorker, initTaskRunner, and color propagation
describe blocks so tests reuse the same mocked os.availableParallelism and
Tinypool implementation (refer to the mocked os.availableParallelism and
Tinypool mockImplementation/run/destroy symbols to locate and remove the
duplicates).

Comment on lines +170 to +242
describe('color propagation to worker processes', () => {
beforeEach(() => {
vi.mocked(os).availableParallelism = vi.fn().mockReturnValue(4);
vi.mocked(Tinypool).mockImplementation(function (this: unknown) {
(this as Record<string, unknown>).run = vi.fn();
(this as Record<string, unknown>).destroy = vi.fn();
return this as Tinypool;
});
});

it('should propagate NO_COLOR to worker when NO_COLOR env is set', () => {
const originalNoColor = process.env.NO_COLOR;
process.env.NO_COLOR = '1';
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
NO_COLOR: '1',
}),
}),
);
} finally {
if (originalNoColor === undefined) {
delete process.env.NO_COLOR;
} else {
process.env.NO_COLOR = originalNoColor;
}
}
});

it('should propagate NO_COLOR to worker when --no-color is in argv', () => {
const originalArgv = process.argv;
process.argv = [...originalArgv, '--no-color'];
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
NO_COLOR: '1',
}),
}),
);
} finally {
process.argv = originalArgv;
}
});

it('should set FORCE_COLOR when colors are not disabled', () => {
const originalNoColor = process.env.NO_COLOR;
delete process.env.NO_COLOR;
const originalArgv = process.argv;
process.argv = originalArgv.filter((arg) => arg !== '--no-color');
try {
createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });

expect(Tinypool).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
FORCE_COLOR: expect.any(String),
}),
}),
);
} finally {
if (originalNoColor !== undefined) {
process.env.NO_COLOR = originalNoColor;
}
process.argv = originalArgv;
}
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing test coverage for FORCE_COLOR leak when NO_COLOR is active.

The three new tests cover the positive propagation cases, but there is no test verifying that a pre-existing FORCE_COLOR in the parent environment is absent from the child env when NO_COLOR should take effect. This is precisely the defect described in the processConcurrency.ts comment above — the test suite would not catch it.

Add a fourth test:

🧪 Suggested additional test
+    it('should not carry FORCE_COLOR into worker when NO_COLOR is active', () => {
+      const originalNoColor = process.env.NO_COLOR;
+      const originalForceColor = process.env.FORCE_COLOR;
+      process.env.NO_COLOR = '1';
+      process.env.FORCE_COLOR = '1'; // simulate CI/parent setting FORCE_COLOR
+      try {
+        createWorkerPool({ numOfTasks: 100, workerType: 'fileProcess', runtime: 'child_process' });
+
+        const callArgs = vi.mocked(Tinypool).mock.calls[0][0] as { env: Record<string, string> };
+        expect(callArgs.env.NO_COLOR).toBe('1');
+        expect(callArgs.env.FORCE_COLOR).toBeUndefined();
+      } finally {
+        if (originalNoColor === undefined) delete process.env.NO_COLOR;
+        else process.env.NO_COLOR = originalNoColor;
+        if (originalForceColor === undefined) delete process.env.FORCE_COLOR;
+        else process.env.FORCE_COLOR = originalForceColor;
+      }
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/shared/processConcurrency.test.ts` around lines 170 - 242, Add a test
in processConcurrency.test.ts that ensures when NO_COLOR is active (either via
process.env.NO_COLOR = '1' or via '--no-color' in process.argv) any pre-existing
FORCE_COLOR in the parent env is not passed to workers: set
process.env.FORCE_COLOR = '1' before calling createWorkerPool, set NO_COLOR (or
argv) so the code should drop FORCE_COLOR, call createWorkerPool, and assert
Tinypool was called with env not containing FORCE_COLOR (or containing NO_COLOR
and not FORCE_COLOR). Use the existing test patterns and restore process.env and
process.argv afterward; reference createWorkerPool and Tinypool to locate where
to add the assertion.

@hztBUAA
Copy link
Copy Markdown
Author

hztBUAA commented Feb 25, 2026

Addressed the actionable review in commit 169e078b.

Changes:

  • Fixed child-process env propagation to prevent FORCE_COLOR leaking when NO_COLOR is enabled.
  • Added regression test should not leak FORCE_COLOR when NO_COLOR is enabled in tests/shared/processConcurrency.test.ts.

@hztBUAA
Copy link
Copy Markdown
Author

hztBUAA commented Feb 25, 2026

Follow-up after running full local verification in a project-specific environment.

Updated branch head: 169e078b

Validation run (from repo root):

  • npm run lint-ts
  • npm test -- tests/shared/processConcurrency.test.ts
  • npm run build

Results:

  • Type check (tsgo --noEmit): pass
  • Vitest: 14 passed in tests/shared/processConcurrency.test.ts
  • Build (tsc -p tsconfig.build.json): pass

This PR is fully validated locally and includes regression coverage for the FORCE_COLOR leak case when NO_COLOR is active.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support solarized color scheme

2 participants