Skip to content

[Bug]: Workers force FORCE_COLOR=1 into spawned child env, ignoring parent NO_COLOR / FORCE_COLOR=0 #40683

@veged

Description

@veged

Context

Filing this as a follow-up to #40006 (closed), where a maintainer asked for a
standalone repository with a minimal reproduction:

When you pass FORCE_COLOR=0 or NO_COLOR=1 we just promise you to not send
ansi color into your stdout/stderr, which we hold. The fact that there are
worker processes is Playwright's implementation detail. If you CI is getting
ansi in stdout despite passing FORCE_COLOR=0, please file an issue with a
repro.

@pavelfeldman, #40006 (comment)

Repro repository: https://github.com/veged/playwright-force-color-repro

Reproduction

git clone https://github.com/veged/playwright-force-color-repro
cd playwright-force-color-repro
npm ci
npx playwright install chromium
npm run repro

The repro script runs:

CI=1 FORCE_COLOR=0 NO_COLOR=1 PLAYWRIGHT_FORCE_TTY=0 \
  playwright test tests/repro.spec.js --reporter=dot

The test (tests/repro.spec.js) spawns two node -e ... children from inside
the worker with { env: process.env }:

  • the first prints the FORCE_COLOR / NO_COLOR values it actually observes
    plus a sample string (ANSI only when FORCE_COLOR && FORCE_COLOR !== '0'),
    surfaced via console.log as worker-child …;
  • the second writes raw ANSI bytes directly to stdout
    (\u001b[31mRAW_RED\u001b[39m) via process.stdout.write, surfaced as
    worker-raw ….

A push-triggered GitHub Actions workflow (.github/workflows/repro.yml) runs
the same scenario on ubuntu-latest with Node 24, so the failure is visible
in CI logs as well. Latest run on @playwright/test@1.59.1:
https://github.com/veged/playwright-force-color-repro/actions/runs/25467296330

Expected

With FORCE_COLOR=0 NO_COLOR=1 set in the parent process, the worker should
not override these, and Playwright's host should not let ANSI from a
worker's child reach the final stdout. The two lines should look like:

worker-child {"forceColor":"0","noColor":"1","sample":"RED"}
worker-raw RAW_RED

Actual

The worker process tree carries FORCE_COLOR=1, so the first child reports
ANSI-on env, and raw ANSI bytes from the second child reach the final CI
stdout unstripped. Node itself also emits a warning that NO_COLOR is being
ignored because FORCE_COLOR is set:

(node:3010) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
worker-child {"forceColor":"1","noColor":"1","sample":"\u001b[31mRED\u001b[39m"}
worker-raw ^[[31mRAW_RED^[[39m

i.e. the worker injects FORCE_COLOR=1 into its environment (which is then
inherited by anything the test spawns), and ANSI emitted by a worker-spawned
child is not stripped from the final stdout. NO_COLOR=1 is preserved but
FORCE_COLOR=1 wins for libraries that check it first (chalk,
supports-color, etc.) — and Node now states this explicitly via the warning
above.

Suspected source

packages/playwright/src/runner/workerHost.ts (~line 52) sets FORCE_COLOR=1
unconditionally on the worker env regardless of whether the parent process
explicitly disabled colors.

Environment

Notes

  • PLAYWRIGHT_FORCE_TTY=0 is set in the repro to rule out the TTY-detection
    path; the override happens regardless.
  • The worker-raw line specifically addresses the contract articulated in
    [Bug]: Playwright workers force ANSI colors via FORCE_COLOR=1, breaking color-disable behavior in CI/CD #40006 (FORCE_COLOR=0 / NO_COLOR=1 should not result in ANSI in final
    stdout/stderr): a child spawned from inside a worker writes raw ANSI bytes
    to stdout, and they reach the final GitHub Actions logs unstripped.
  • The Node warning The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set. is direct evidence from the runtime that the worker's
    injected FORCE_COLOR=1 is overriding the caller's NO_COLOR=1.
  • The behavior breaks pipelines that explicitly disable colors via
    NO_COLOR / FORCE_COLOR=0 — any tooling spawned from within a test
    receives ANSI-on env and emits escape codes into CI logs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions