Environment
env-runner: 0.1.16
- Runner:
node-worker (NodeWorkerEnvRunner, node:worker_threads)
Summary
Anything that logs from inside a dev worker loses ANSI color (and cannot detect terminal width), even when the host process is an interactive TTY.
The runner starts the worker with the host env inherited verbatim and nothing else:
// dist/_chunks/node-worker-runner.mjs
const worker = new Worker(this._workerEntry, {
env: { ...process.env },
workerData: { name: this._name, ...this._data },
});
In a node:worker_threads worker, process.stdout is a pipe to the host — so process.stdout.isTTY and process.stdout.columns are undefined. Worker console.log output still reaches the terminal, but the standard color/width detection everyone uses (isTTY) reports "not a terminal", so ANSI is stripped and width-aware output can't size itself.
Net effect: framework logs printed from the host process are colored; anything printed from application/runtime code running in the worker is not.
Reproduction
// worker-tty.mjs
import { Worker, isMainThread } from "node:worker_threads";
if (isMainThread) {
console.log("[main] ", process.stdout.isTTY, process.stdout.columns);
new Worker(new URL(import.meta.url), { env: { ...process.env } }); // same as env-runner
} else {
console.log("[worker]", process.stdout.isTTY, process.stdout.columns, process.env.FORCE_COLOR);
}
Run from a real terminal:
[main] true 173
[worker] undefined undefined undefined
The env is inherited, so FORCE_COLOR set on the host does reach the worker — it's just never set.
Proposed fix
When spawning a worker, propagate the host's terminal capabilities into the worker env (respecting existing FORCE_COLOR / NO_COLOR), the way tinypool/vitest, execa, npm, etc. do:
const env = { ...process.env };
if (!("FORCE_COLOR" in env) && !env.NO_COLOR && process.stdout.isTTY) {
env.FORCE_COLOR = "1";
}
if (!("COLUMNS" in env) && process.stdout.columns) {
env.COLUMNS = String(process.stdout.columns);
}
const worker = new Worker(this._workerEntry, { env, workerData: { ... } });
Then worker-side code using the standard NO_COLOR / FORCE_COLOR → isTTY cascade colorizes correctly, and width-aware output can read COLUMNS.
Notes:
- Same reasoning applies to the child-process runners (Deno) — anything where the child's stdout isn't the host TTY.
COLUMNS is a static snapshot; propagating resize is a nice-to-have but not required for the color fix.
Context
Surfaced via Nitro's experimental tracingLogger (a per-request span waterfall) which renders colored ANSI + adapts to terminal width — both are disabled in nitro dev purely because the worker reports no TTY. Nitro-side it now honors FORCE_COLOR/NO_COLOR/COLUMNS, so once the runner propagates them it "just works".
Environment
env-runner:0.1.16node-worker(NodeWorkerEnvRunner,node:worker_threads)Summary
Anything that logs from inside a dev worker loses ANSI color (and cannot detect terminal width), even when the host process is an interactive TTY.
The runner starts the worker with the host env inherited verbatim and nothing else:
In a
node:worker_threadsworker,process.stdoutis a pipe to the host — soprocess.stdout.isTTYandprocess.stdout.columnsareundefined. Workerconsole.logoutput still reaches the terminal, but the standard color/width detection everyone uses (isTTY) reports "not a terminal", so ANSI is stripped and width-aware output can't size itself.Net effect: framework logs printed from the host process are colored; anything printed from application/runtime code running in the worker is not.
Reproduction
Run from a real terminal:
The env is inherited, so
FORCE_COLORset on the host does reach the worker — it's just never set.Proposed fix
When spawning a worker, propagate the host's terminal capabilities into the worker env (respecting existing
FORCE_COLOR/NO_COLOR), the waytinypool/vitest,execa,npm, etc. do:Then worker-side code using the standard
NO_COLOR/FORCE_COLOR→isTTYcascade colorizes correctly, and width-aware output can readCOLUMNS.Notes:
COLUMNSis a static snapshot; propagating resize is a nice-to-have but not required for the color fix.Context
Surfaced via Nitro's experimental
tracingLogger(a per-request span waterfall) which renders colored ANSI + adapts to terminal width — both are disabled innitro devpurely because the worker reports no TTY. Nitro-side it now honorsFORCE_COLOR/NO_COLOR/COLUMNS, so once the runner propagates them it "just works".