From 9aea47537f9635481b495f01d08840e8d5cc3d47 Mon Sep 17 00:00:00 2001 From: Siornya Date: Thu, 12 Nov 2026 08:57:10 +0000 Subject: [PATCH 1/3] fix(run): preserve colors for uncached tasks with piped output Set FORCE_COLOR=1 for uncached piped tasks when the parent terminal supports colors and no explicit color controls are set. Add regression tests and a changelog entry. Refs voidzero-dev/vite-plus#2911 Co-authored-by: Codex (GPT-6) --- CHANGELOG.md | 2 + crates/vt/src/session/execute/mod.rs | 22 ++++-- .../fixtures/color_env_handling/package.json | 7 +- .../color_env_handling/snapshots.toml | 73 +++++++++++++++++++ .../snapshots/uncached_explicit_task_color.md | 16 ++++ .../snapshots/uncached_force_color_zero.md | 16 ++++ ...d_inherited_stdio_keeps_color_detection.md | 8 ++ .../snapshots/uncached_no_color.md | 16 ++++ .../snapshots/uncached_node_disable_colors.md | 16 ++++ .../snapshots/uncached_piped_colors.md | 16 ++++ ...uncached_preserves_explicit_force_color.md | 16 ++++ .../snapshots/uncached_redirected_stdout.md | 18 +++++ 12 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_piped_colors.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5cfa856..fe3d859c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +- **Fixed** Uncached tasks preserve terminal colors with `vp run --log labeled` and `--log grouped`, including Vitest coverage tables. Explicit color controls and redirected stdout remain respected ([#???](https://github.com/voidzero-dev/vite-task/pull/???)). + - **Fixed** On Windows, tasks with automatic file-access tracking can now run Git Bash's `sh` and other MSYS2 or Cygwin programs, which aborted with `fatal error - MapViewOfFileEx 'shared.5'(…) Win32 error 487` ([#830](https://github.com/voidzero-dev/vite-task/issues/830), [#952](https://github.com/voidzero-dev/vite-task/pull/952)). - **Fixed** On Linux, cached tasks no longer fail to spawn child processes in restricted sandboxes, for example rootless bubblewrap that denies the `seccomp` syscall. When the `seccomp` filter for file-access tracking cannot be installed, the process runs untracked instead and the run is reported as not cached. The not-cached message now reads "automatic file tracking couldn't record everything this task did", which fits both this case and a full tracking channel ([#700](https://github.com/voidzero-dev/vite-task/issues/700), [#701](https://github.com/voidzero-dev/vite-task/pull/701)). - **Fixed** `vp run ` in a package that doesn't define the task now reports `Task "" not found` when another package's `vite.config.*` fails to load, instead of failing with that package's error. Showing the tasks to choose from loads every package's config, so the task selector or task list is skipped then ([#987](https://github.com/voidzero-dev/vite-task/pull/987)). diff --git a/crates/vt/src/session/execute/mod.rs b/crates/vt/src/session/execute/mod.rs index 86cc5115e..b23bd726d 100644 --- a/crates/vt/src/session/execute/mod.rs +++ b/crates/vt/src/session/execute/mod.rs @@ -21,10 +21,11 @@ use std::{ use futures_util::future::LocalBoxFuture; use tokio_util::sync::CancellationToken; +use vt_casefold::EnvName; use vt_glob::path::PathGlobSet; use vt_ipc_shared::NODE_CLIENT_PATH_ENV_NAME; use vt_path::{AbsolutePath, RelativePathBuf}; -use vt_plan::{SpawnExecution, cache_metadata::CacheMetadata}; +use vt_plan::{SpawnCommand, SpawnExecution, cache_metadata::CacheMetadata}; use vt_server::{Recorder, Reports, ServerHandle, StopAccepting, serve}; use self::{ @@ -205,9 +206,10 @@ impl<'a> ExecutionMode<'a> { } /// The extra envs to inject into the child: IPC connection info + the - /// napi addon path runner-aware tools `require()`. Empty when execution - /// is uncached. - fn injected_envs(&self) -> Vec<(&OsStr, &OsStr)> { + /// napi addon path runner-aware tools `require()`. Uncached piped tasks + /// also preserve the parent terminal's color support unless the task + /// explicitly controls colors. + fn injected_envs(&self, command: &SpawnCommand) -> Vec<(&OsStr, &OsStr)> { match self { Self::Cached { state: CacheState { tracking: t, .. }, .. } => { let mut envs: Vec<(&OsStr, &OsStr)> = @@ -218,6 +220,16 @@ impl<'a> ExecutionMode<'a> { )); envs } + Self::Uncached { pipe_writers: Some(_) } + if super::stdout_supports_color() + && !["FORCE_COLOR", "NO_COLOR", "NODE_DISABLE_COLORS"].iter().any(|name| { + command.spawn_envs.contains_key(EnvName::from_ref(OsStr::new(name))) + }) => + { + // Piping hides the terminal from color detection in child tools. + // This is display policy, so keep it out of the cached plan. + vec![(OsStr::new("FORCE_COLOR"), OsStr::new("1"))] + } Self::Uncached { .. } => Vec::new(), } } @@ -498,7 +510,7 @@ async fn run( fspy_enabled, spawn_stdio, fast_fail_token.clone(), - mode.injected_envs(), + mode.injected_envs(&spawn_execution.spawn_command), ) .await .map_err(|err| Report::failed(ExecutionError::Spawn(err)))?; diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json index cf301ba7f..bf38285e7 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json @@ -1,4 +1,9 @@ { "name": "color-env-handling", - "private": true + "private": true, + "scripts": { + "uncached-color": "vtt print-color green coverage-row", + "uncached-force": "vtt print-env FORCE_COLOR", + "explicit-no-color": "FORCE_COLOR=0 vtt print-env FORCE_COLOR" + } } diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml index 85c1436a7..d3f1a6cd6 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml @@ -45,3 +45,76 @@ argv = ["vt", "run", "print-colored-b"] envs = [["FORCE_COLOR", "1"]] comment = "task B — cache hit replayed; formatted snapshot proves the cached bytes were coloured, even though the cache-miss run (above) showed them as plain text" formatted-snapshot = true + +[[e2e]] +name = "uncached_piped_colors" + +[[e2e.steps]] +argv = ["vt", "run", "--log", "labeled", "uncached-color"] +formatted-snapshot = true + +[[e2e.steps]] +argv = ["vt", "run", "--log", "grouped", "uncached-color"] +formatted-snapshot = true + +[[e2e]] +name = "uncached_force_color_zero" + +[[e2e.steps]] +argv = ["vt", "run", "--log", "labeled", "uncached-force"] +envs = [["FORCE_COLOR", "0"]] + +[[e2e.steps]] +argv = ["vt", "run", "--log", "grouped", "uncached-force"] +envs = [["FORCE_COLOR", "0"]] + +[[e2e]] +name = "uncached_no_color" + +[[e2e.steps]] +argv = ["vt", "run", "--log", "labeled", "uncached-force"] +envs = [["NO_COLOR", "1"]] + +[[e2e.steps]] +argv = ["vt", "run", "--log", "grouped", "uncached-force"] +envs = [["NO_COLOR", "1"]] + +[[e2e]] +name = "uncached_node_disable_colors" + +[[e2e.steps]] +argv = ["vt", "run", "--log", "labeled", "uncached-force"] +envs = [["NODE_DISABLE_COLORS", "1"]] + +[[e2e.steps]] +argv = ["vt", "run", "--log", "grouped", "uncached-force"] +envs = [["NODE_DISABLE_COLORS", "1"]] + +[[e2e]] +name = "uncached_explicit_task_color" + +[[e2e.steps]] +argv = ["vt", "run", "--log", "labeled", "explicit-no-color"] + +[[e2e.steps]] +argv = ["vt", "run", "--log", "grouped", "explicit-no-color"] + +[[e2e]] +name = "uncached_redirected_stdout" + +[[e2e.steps]] +argv = ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "labeled", "uncached-force"] + +[[e2e.steps]] +argv = ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "grouped", "uncached-force"] + +[[e2e]] +name = "uncached_inherited_stdio_keeps_color_detection" +steps = [["vt", "run", "--log", "interleaved", "uncached-force"]] + +[[e2e]] +name = "uncached_preserves_explicit_force_color" +steps = [ + { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["FORCE_COLOR", "3"]] }, + { argv = ["vt", "run", "--log", "grouped", "uncached-force"], envs = [["FORCE_COLOR", "3"]] }, +] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md new file mode 100644 index 000000000..1f62716fd --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md @@ -0,0 +1,16 @@ +# uncached_explicit_task_color + +## `vt run --log labeled explicit-no-color` + +``` +[color-env-handling#explicit-no-color] $ FORCE_COLOR=0 vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#explicit-no-color] 0 +``` + +## `vt run --log grouped explicit-no-color` + +``` +[color-env-handling#explicit-no-color] $ FORCE_COLOR=0 vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#explicit-no-color] ── +0 +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md new file mode 100644 index 000000000..338a2a0ec --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md @@ -0,0 +1,16 @@ +# uncached_force_color_zero + +## `FORCE_COLOR=0 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] 0 +``` + +## `FORCE_COLOR=0 vt run --log grouped uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#uncached-force] ── +0 +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md new file mode 100644 index 000000000..b30ef7b13 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md @@ -0,0 +1,8 @@ +# uncached_inherited_stdio_keeps_color_detection + +## `vt run --log interleaved uncached-force` + +``` +$ vtt print-env FORCE_COLOR ⊘ cache disabled +(undefined) +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md new file mode 100644 index 000000000..5553c7ef9 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md @@ -0,0 +1,16 @@ +# uncached_no_color + +## `NO_COLOR=1 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] (undefined) +``` + +## `NO_COLOR=1 vt run --log grouped uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#uncached-force] ── +(undefined) +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md new file mode 100644 index 000000000..d022d59ed --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md @@ -0,0 +1,16 @@ +# uncached_node_disable_colors + +## `NODE_DISABLE_COLORS=1 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] (undefined) +``` + +## `NODE_DISABLE_COLORS=1 vt run --log grouped uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#uncached-force] ── +(undefined) +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_piped_colors.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_piped_colors.md new file mode 100644 index 000000000..49f0c2098 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_piped_colors.md @@ -0,0 +1,16 @@ +# uncached_piped_colors + +## `vt run --log labeled uncached-color` + +``` +\x1b[90m[color-env-handling#uncached-color] \x1b[34m$ vtt print-color green coverage-row \x1b[30m\xe2\x8a\x98 \x1b[90mcache disabled +\x1b[90m[color-env-handling#uncached-color] \x1b[32mcoverage-row +``` + +## `vt run --log grouped uncached-color` + +``` +\x1b[90m[color-env-handling#uncached-color] \x1b[34m$ vtt print-color green coverage-row \x1b[30m\xe2\x8a\x98 \x1b[90mcache disabled +\x1b[90m\xe2\x94\x80\xe2\x94\x80 \x1b[90m[color-env-handling#uncached-color] \x1b[90m\xe2\x94\x80\xe2\x94\x80 +\x1b[32mcoverage-row +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md new file mode 100644 index 000000000..f4e93760e --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md @@ -0,0 +1,16 @@ +# uncached_preserves_explicit_force_color + +## `FORCE_COLOR=3 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] 3 +``` + +## `FORCE_COLOR=3 vt run --log grouped uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#uncached-force] ── +3 +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md new file mode 100644 index 000000000..2ed643dff --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md @@ -0,0 +1,18 @@ +# uncached_redirected_stdout + +## `vtt pipe-stdout -- vt run --log labeled uncached-force` + +``` +--- stdout --- +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] (undefined) +``` + +## `vtt pipe-stdout -- vt run --log grouped uncached-force` + +``` +--- stdout --- +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +── [color-env-handling#uncached-force] ── +(undefined) +``` From fcb02a8cdb4dcc0718a1c1a123680929ba13df75 Mon Sep 17 00:00:00 2001 From: Siornya Date: Thu, 12 Nov 2026 09:24:23 +0000 Subject: [PATCH 2/3] test(run): consolidate uncached color regression tests Consolidate eight cases into three and remove duplicate grouped-mode checks for color controls and stdout redirection. Merge the corresponding snapshots. --- .../color_env_handling/snapshots.toml | 67 +++---------------- .../uncached_explicit_color_controls.md | 36 ++++++++++ .../snapshots/uncached_explicit_task_color.md | 16 ----- .../snapshots/uncached_force_color_zero.md | 16 ----- ...d_inherited_stdio_keeps_color_detection.md | 8 --- .../snapshots/uncached_no_color.md | 16 ----- .../snapshots/uncached_node_disable_colors.md | 16 ----- ...uncached_preserves_explicit_force_color.md | 16 ----- ...t.md => uncached_stdio_color_detection.md} | 8 +-- 9 files changed, 50 insertions(+), 149 deletions(-) create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_color_controls.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md delete mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md rename crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/{uncached_redirected_stdout.md => uncached_stdio_color_detection.md} (50%) diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml index d3f1a6cd6..9a383684e 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml @@ -58,63 +58,18 @@ argv = ["vt", "run", "--log", "grouped", "uncached-color"] formatted-snapshot = true [[e2e]] -name = "uncached_force_color_zero" - -[[e2e.steps]] -argv = ["vt", "run", "--log", "labeled", "uncached-force"] -envs = [["FORCE_COLOR", "0"]] - -[[e2e.steps]] -argv = ["vt", "run", "--log", "grouped", "uncached-force"] -envs = [["FORCE_COLOR", "0"]] - -[[e2e]] -name = "uncached_no_color" - -[[e2e.steps]] -argv = ["vt", "run", "--log", "labeled", "uncached-force"] -envs = [["NO_COLOR", "1"]] - -[[e2e.steps]] -argv = ["vt", "run", "--log", "grouped", "uncached-force"] -envs = [["NO_COLOR", "1"]] - -[[e2e]] -name = "uncached_node_disable_colors" - -[[e2e.steps]] -argv = ["vt", "run", "--log", "labeled", "uncached-force"] -envs = [["NODE_DISABLE_COLORS", "1"]] - -[[e2e.steps]] -argv = ["vt", "run", "--log", "grouped", "uncached-force"] -envs = [["NODE_DISABLE_COLORS", "1"]] - -[[e2e]] -name = "uncached_explicit_task_color" - -[[e2e.steps]] -argv = ["vt", "run", "--log", "labeled", "explicit-no-color"] - -[[e2e.steps]] -argv = ["vt", "run", "--log", "grouped", "explicit-no-color"] - -[[e2e]] -name = "uncached_redirected_stdout" - -[[e2e.steps]] -argv = ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "labeled", "uncached-force"] - -[[e2e.steps]] -argv = ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "grouped", "uncached-force"] - -[[e2e]] -name = "uncached_inherited_stdio_keeps_color_detection" -steps = [["vt", "run", "--log", "interleaved", "uncached-force"]] +name = "uncached_explicit_color_controls" +steps = [ + { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["FORCE_COLOR", "0"]] }, + { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["FORCE_COLOR", "3"]] }, + { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["NO_COLOR", "1"]] }, + { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["NODE_DISABLE_COLORS", "1"]] }, + ["vt", "run", "--log", "labeled", "explicit-no-color"], +] [[e2e]] -name = "uncached_preserves_explicit_force_color" +name = "uncached_stdio_color_detection" steps = [ - { argv = ["vt", "run", "--log", "labeled", "uncached-force"], envs = [["FORCE_COLOR", "3"]] }, - { argv = ["vt", "run", "--log", "grouped", "uncached-force"], envs = [["FORCE_COLOR", "3"]] }, + ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "labeled", "uncached-force"], + ["vt", "run", "--log", "interleaved", "uncached-force"], ] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_color_controls.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_color_controls.md new file mode 100644 index 000000000..145ac369a --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_color_controls.md @@ -0,0 +1,36 @@ +# uncached_explicit_color_controls + +## `FORCE_COLOR=0 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] 0 +``` + +## `FORCE_COLOR=3 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] 3 +``` + +## `NO_COLOR=1 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] (undefined) +``` + +## `NODE_DISABLE_COLORS=1 vt run --log labeled uncached-force` + +``` +[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#uncached-force] (undefined) +``` + +## `vt run --log labeled explicit-no-color` + +``` +[color-env-handling#explicit-no-color] $ FORCE_COLOR=0 vtt print-env FORCE_COLOR ⊘ cache disabled +[color-env-handling#explicit-no-color] 0 +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md deleted file mode 100644 index 1f62716fd..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_explicit_task_color.md +++ /dev/null @@ -1,16 +0,0 @@ -# uncached_explicit_task_color - -## `vt run --log labeled explicit-no-color` - -``` -[color-env-handling#explicit-no-color] $ FORCE_COLOR=0 vtt print-env FORCE_COLOR ⊘ cache disabled -[color-env-handling#explicit-no-color] 0 -``` - -## `vt run --log grouped explicit-no-color` - -``` -[color-env-handling#explicit-no-color] $ FORCE_COLOR=0 vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#explicit-no-color] ── -0 -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md deleted file mode 100644 index 338a2a0ec..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_force_color_zero.md +++ /dev/null @@ -1,16 +0,0 @@ -# uncached_force_color_zero - -## `FORCE_COLOR=0 vt run --log labeled uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -[color-env-handling#uncached-force] 0 -``` - -## `FORCE_COLOR=0 vt run --log grouped uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#uncached-force] ── -0 -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md deleted file mode 100644 index b30ef7b13..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_inherited_stdio_keeps_color_detection.md +++ /dev/null @@ -1,8 +0,0 @@ -# uncached_inherited_stdio_keeps_color_detection - -## `vt run --log interleaved uncached-force` - -``` -$ vtt print-env FORCE_COLOR ⊘ cache disabled -(undefined) -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md deleted file mode 100644 index 5553c7ef9..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_no_color.md +++ /dev/null @@ -1,16 +0,0 @@ -# uncached_no_color - -## `NO_COLOR=1 vt run --log labeled uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -[color-env-handling#uncached-force] (undefined) -``` - -## `NO_COLOR=1 vt run --log grouped uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#uncached-force] ── -(undefined) -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md deleted file mode 100644 index d022d59ed..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_node_disable_colors.md +++ /dev/null @@ -1,16 +0,0 @@ -# uncached_node_disable_colors - -## `NODE_DISABLE_COLORS=1 vt run --log labeled uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -[color-env-handling#uncached-force] (undefined) -``` - -## `NODE_DISABLE_COLORS=1 vt run --log grouped uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#uncached-force] ── -(undefined) -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md deleted file mode 100644 index f4e93760e..000000000 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_preserves_explicit_force_color.md +++ /dev/null @@ -1,16 +0,0 @@ -# uncached_preserves_explicit_force_color - -## `FORCE_COLOR=3 vt run --log labeled uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -[color-env-handling#uncached-force] 3 -``` - -## `FORCE_COLOR=3 vt run --log grouped uncached-force` - -``` -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#uncached-force] ── -3 -``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md similarity index 50% rename from crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md rename to crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md index 2ed643dff..adbaa2c4e 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_redirected_stdout.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md @@ -1,4 +1,4 @@ -# uncached_redirected_stdout +# uncached_stdio_color_detection ## `vtt pipe-stdout -- vt run --log labeled uncached-force` @@ -8,11 +8,9 @@ [color-env-handling#uncached-force] (undefined) ``` -## `vtt pipe-stdout -- vt run --log grouped uncached-force` +## `vt run --log interleaved uncached-force` ``` ---- stdout --- -[color-env-handling#uncached-force] $ vtt print-env FORCE_COLOR ⊘ cache disabled -── [color-env-handling#uncached-force] ── +$ vtt print-env FORCE_COLOR ⊘ cache disabled (undefined) ``` From d751ab09f657b7ca572dee4550f34a4a2282d9a4 Mon Sep 17 00:00:00 2001 From: Siornya Date: Thu, 12 Nov 2026 10:22:59 +0000 Subject: [PATCH 3/3] fix(run): preserve color controls in shell-fallback tasks Mark shell-fallback commands during planning and skip automatic FORCE_COLOR injection for uncached executions of those commands. Co-authored-by: Codex (GPT-6) --- CHANGELOG.md | 2 +- crates/vt/src/session/execute/mod.rs | 5 ++-- crates/vt/src/session/reporter/mod.rs | 1 + .../fixtures/color_env_handling/package.json | 3 ++- .../color_env_handling/snapshots.toml | 4 ++++ .../uncached_stdio_color_detection.md | 24 +++++++++++++++++++ crates/vt_plan/src/lib.rs | 5 ++++ crates/vt_plan/src/plan.rs | 4 +++- 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3d859c6..87b438f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -- **Fixed** Uncached tasks preserve terminal colors with `vp run --log labeled` and `--log grouped`, including Vitest coverage tables. Explicit color controls and redirected stdout remain respected ([#???](https://github.com/voidzero-dev/vite-task/pull/???)). +- **Fixed** Uncached tasks executed directly now preserve colors with `vp run --log labeled` and `--log grouped`, including Vitest coverage tables ([#997](https://github.com/voidzero-dev/vite-task/pull/997)). - **Fixed** On Windows, tasks with automatic file-access tracking can now run Git Bash's `sh` and other MSYS2 or Cygwin programs, which aborted with `fatal error - MapViewOfFileEx 'shared.5'(…) Win32 error 487` ([#830](https://github.com/voidzero-dev/vite-task/issues/830), [#952](https://github.com/voidzero-dev/vite-task/pull/952)). - **Fixed** On Linux, cached tasks no longer fail to spawn child processes in restricted sandboxes, for example rootless bubblewrap that denies the `seccomp` syscall. When the `seccomp` filter for file-access tracking cannot be installed, the process runs untracked instead and the run is reported as not cached. The not-cached message now reads "automatic file tracking couldn't record everything this task did", which fits both this case and a full tracking channel ([#700](https://github.com/voidzero-dev/vite-task/issues/700), [#701](https://github.com/voidzero-dev/vite-task/pull/701)). diff --git a/crates/vt/src/session/execute/mod.rs b/crates/vt/src/session/execute/mod.rs index b23bd726d..b4ef58789 100644 --- a/crates/vt/src/session/execute/mod.rs +++ b/crates/vt/src/session/execute/mod.rs @@ -221,13 +221,12 @@ impl<'a> ExecutionMode<'a> { envs } Self::Uncached { pipe_writers: Some(_) } - if super::stdout_supports_color() + if !command.shell_fallback + && super::stdout_supports_color() && !["FORCE_COLOR", "NO_COLOR", "NODE_DISABLE_COLORS"].iter().any(|name| { command.spawn_envs.contains_key(EnvName::from_ref(OsStr::new(name))) }) => { - // Piping hides the terminal from color detection in child tools. - // This is display policy, so keep it out of the cached plan. vec![(OsStr::new("FORCE_COLOR"), OsStr::new("1"))] } Self::Uncached { .. } => Vec::new(), diff --git a/crates/vt/src/session/reporter/mod.rs b/crates/vt/src/session/reporter/mod.rs index 6c03c3d4a..a705e9fc6 100644 --- a/crates/vt/src/session/reporter/mod.rs +++ b/crates/vt/src/session/reporter/mod.rs @@ -526,6 +526,7 @@ pub mod test_fixtures { kind: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(SpawnExecution { cache_metadata: None, spawn_command: SpawnCommand { + shell_fallback: false, program_path: test_path(), args: Arc::from([]), spawn_envs: Arc::new(BTreeMap::new()), diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json index bf38285e7..b3a5751e2 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/package.json @@ -4,6 +4,7 @@ "scripts": { "uncached-color": "vtt print-color green coverage-row", "uncached-force": "vtt print-env FORCE_COLOR", - "explicit-no-color": "FORCE_COLOR=0 vtt print-env FORCE_COLOR" + "explicit-no-color": "FORCE_COLOR=0 vtt print-env FORCE_COLOR", + "shell-force": "vtt print-env FORCE_COLOR > shell-force.txt" } } diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml index 9a383684e..7a07d08b3 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots.toml @@ -70,6 +70,10 @@ steps = [ [[e2e]] name = "uncached_stdio_color_detection" steps = [ + ["vt", "run", "--log", "labeled", "shell-force"], + ["vtt", "print-file", "shell-force.txt"], + ["vt", "run", "--log", "grouped", "shell-force"], + ["vtt", "print-file", "shell-force.txt"], ["vtt", "pipe-stdout", "--", "vt", "run", "--log", "labeled", "uncached-force"], ["vt", "run", "--log", "interleaved", "uncached-force"], ] diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md index adbaa2c4e..9118584f1 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/color_env_handling/snapshots/uncached_stdio_color_detection.md @@ -1,5 +1,29 @@ # uncached_stdio_color_detection +## `vt run --log labeled shell-force` + +``` +[color-env-handling#shell-force] $ vtt print-env FORCE_COLOR > shell-force.txt ⊘ cache disabled +``` + +## `vtt print-file shell-force.txt` + +``` +(undefined) +``` + +## `vt run --log grouped shell-force` + +``` +[color-env-handling#shell-force] $ vtt print-env FORCE_COLOR > shell-force.txt ⊘ cache disabled +``` + +## `vtt print-file shell-force.txt` + +``` +(undefined) +``` + ## `vtt pipe-stdout -- vt run --log labeled uncached-force` ``` diff --git a/crates/vt_plan/src/lib.rs b/crates/vt_plan/src/lib.rs index 6612b7375..baeac66f5 100644 --- a/crates/vt_plan/src/lib.rs +++ b/crates/vt_plan/src/lib.rs @@ -54,6 +54,11 @@ pub struct SpawnCommand { #[serde(serialize_with = "serialize_envs")] pub spawn_envs: Arc>, Arc>>, + /// Whether the runner delegates an unparsed task script to the system shell. + /// Color controls inside the script are not represented in `spawn_envs`. + #[serde(skip)] + pub shell_fallback: bool, + /// Current working directory pub cwd: Arc, } diff --git a/crates/vt_plan/src/plan.rs b/crates/vt_plan/src/plan.rs index 616d830a9..bf7b20169 100644 --- a/crates/vt_plan/src/plan.rs +++ b/crates/vt_plan/src/plan.rs @@ -388,7 +388,7 @@ async fn plan_task_as_execution_node( context.resolved_global_cache(), ), }; - let spawn_execution = plan_spawn_execution( + let mut spawn_execution = plan_spawn_execution( context.workspace_path(), Some(ExecutionCacheKey::UserTask { task_name: task_node.task_display.task_name.clone(), @@ -408,6 +408,7 @@ async fn plan_task_as_execution_node( Arc::clone(&*SHELL_PROGRAM_PATH), SHELL_ARGS.iter().map(|s| Str::from(*s)).chain(std::iter::once(script)).collect(), )?; + spawn_execution.spawn_command.shell_fallback = true; items.push(ExecutionItem { execution_item_display, kind: ExecutionItemKind::Leaf(LeafExecutionKind::Spawn(spawn_execution)), @@ -723,6 +724,7 @@ fn plan_spawn_execution( Ok(SpawnExecution { spawn_command: SpawnCommand { + shell_fallback: false, program_path, args: Arc::clone(&args), cwd,