From b475dab358a631bbbb3b11120098579fd448d547 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 09:56:08 +0000 Subject: [PATCH] test(cli): route 14 e2e spawners through childEnv(), 18 -> 3 on the child-env ratchet The `check:cli-test-child-env` gate (#11341) baselined 18 bulk `process.env` copies reaching spawned children under `packages/cli/test/**`. This is the cheap-batch slice of that burn-down: 15 references across 14 files, each `{ ...process.env, X }` replaced with `childEnv({ X })`, the choke point from #11267 that strips the vitest worker family (`TEST`, `VITEST`, `VITEST_*`) before the overrides are applied. `scripts/cli-test-child-env.baseline.json` drops the 14 repaired keys in the same commit -- the ratchet fails on a stale ceiling too, so leaving 18 there would silently license 15 new leaks. Left in the ledger deliberately: the three `serve-*` files the card flags for the crypto/auth re-check (`$HOME/.objectstack/dev-crypto-key`, the `OS_SECRET_KEY` precedent), which need a second slice. The two `DELIBERATE` sites are untouched. Every one of the 14 files was run individually after the edit -- 147 tests, all green -- so no child in this slice turned out to depend on an inherited runner variable. Part of #11596 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR --- .../cli/test/cloud-login-json-ndjson.e2e.test.ts | 3 ++- .../cli/test/generate-agent-retired.e2e.test.ts | 3 ++- packages/cli/test/generate-skill.e2e.test.ts | 3 ++- .../cli/test/hook-body-build-reach.e2e.test.ts | 3 ++- .../test/init-created-files-summary.e2e.test.ts | 3 ++- .../cli/test/invocation-loudness.e2e.test.ts | 3 ++- packages/cli/test/json-stdout-purity.e2e.test.ts | 3 ++- packages/cli/test/login-json-ndjson.e2e.test.ts | 3 ++- .../test/login-json-noninteractive.e2e.test.ts | 3 ++- packages/cli/test/migrate-exit-code.e2e.test.ts | 3 ++- packages/cli/test/migrate-meta.e2e.test.ts | 5 +++-- .../cli/test/qa-empty-glob-exit-code.e2e.test.ts | 3 ++- .../validate-json-warning-parity.e2e.test.ts | 3 ++- .../test/validate-top-level-strict.e2e.test.ts | 3 ++- scripts/cli-test-child-env.baseline.json | 16 +--------------- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/cli/test/cloud-login-json-ndjson.e2e.test.ts b/packages/cli/test/cloud-login-json-ndjson.e2e.test.ts index f2586a1082..bb047ef161 100644 --- a/packages/cli/test/cloud-login-json-ndjson.e2e.test.ts +++ b/packages/cli/test/cloud-login-json-ndjson.e2e.test.ts @@ -70,6 +70,7 @@ import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -278,7 +279,7 @@ async function runCloudDeviceLogin(opts: { 'script', // -q quiet, -e propagate the child's exit status, -c the command. ['-qec', shell, '/dev/null'], - { env: { ...process.env, HOME: home, NO_COLOR: '1' }, maxBuffer: 32 * 1024 * 1024 }, + { env: childEnv({ HOME: home, NO_COLOR: '1' }), maxBuffer: 32 * 1024 * 1024 }, (err) => res(err ? Number((err as { code?: unknown }).code ?? 1) : 0), ); }); diff --git a/packages/cli/test/generate-agent-retired.e2e.test.ts b/packages/cli/test/generate-agent-retired.e2e.test.ts index 7f8de57dac..005dc87c49 100644 --- a/packages/cli/test/generate-agent-retired.e2e.test.ts +++ b/packages/cli/test/generate-agent-retired.e2e.test.ts @@ -37,6 +37,7 @@ import { existsSync, mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -56,7 +57,7 @@ function runTsx(args: string[], cwd: string): Promise { execFile( TSX, args, - { cwd, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ // `err.code` is the real exit status; null/undefined means the child diff --git a/packages/cli/test/generate-skill.e2e.test.ts b/packages/cli/test/generate-skill.e2e.test.ts index a4718adaf6..b1e5425e74 100644 --- a/packages/cli/test/generate-skill.e2e.test.ts +++ b/packages/cli/test/generate-skill.e2e.test.ts @@ -81,6 +81,7 @@ import { tmpdir } from 'node:os'; import { dirname, join, matchesGlob, relative, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import { DEFAULT_METADATA_TYPE_REGISTRY } from '@objectstack/spec/kernel'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -114,7 +115,7 @@ function runTsx(args: string[], cwd: string): Promise { execFile( TSX, args, - { cwd, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ // `err.code` is the real exit status; null/undefined means the child diff --git a/packages/cli/test/hook-body-build-reach.e2e.test.ts b/packages/cli/test/hook-body-build-reach.e2e.test.ts index ec9d0de562..a5b1b75eb0 100644 --- a/packages/cli/test/hook-body-build-reach.e2e.test.ts +++ b/packages/cli/test/hook-body-build-reach.e2e.test.ts @@ -51,6 +51,7 @@ import { mkdtempSync, rmSync, writeFileSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -67,7 +68,7 @@ function runCli(args: string[], cwd: string): Promise { execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0, diff --git a/packages/cli/test/init-created-files-summary.e2e.test.ts b/packages/cli/test/init-created-files-summary.e2e.test.ts index 77d2fc8430..e9de4c8884 100644 --- a/packages/cli/test/init-created-files-summary.e2e.test.ts +++ b/packages/cli/test/init-created-files-summary.e2e.test.ts @@ -57,6 +57,7 @@ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, chmodSync, readdirSync } import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -75,7 +76,7 @@ function runCli(args: string[], cwd: string, env: Record): Promi execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1', ...env } }, + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', ...env }) }, (err, stdout, stderr) => { resolvePromise({ // The real exit status, not truthiness of `err` — a non-zero code diff --git a/packages/cli/test/invocation-loudness.e2e.test.ts b/packages/cli/test/invocation-loudness.e2e.test.ts index 48f3fb3c8e..521dd5e6af 100644 --- a/packages/cli/test/invocation-loudness.e2e.test.ts +++ b/packages/cli/test/invocation-loudness.e2e.test.ts @@ -46,6 +46,7 @@ import { mkdtempSync, rmSync, symlinkSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const BIN = resolve(HERE, '../bin/run.js'); @@ -67,7 +68,7 @@ function runTsx(args: string[], cwd: string): Promise { execFile( TSX, args, - { cwd, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ // `err.code` is the real exit status; null/undefined means the child diff --git a/packages/cli/test/json-stdout-purity.e2e.test.ts b/packages/cli/test/json-stdout-purity.e2e.test.ts index a66dc2f261..9612f1aa13 100644 --- a/packages/cli/test/json-stdout-purity.e2e.test.ts +++ b/packages/cli/test/json-stdout-purity.e2e.test.ts @@ -62,6 +62,7 @@ import { mkdtempSync, rmSync, writeFileSync, readFileSync, readdirSync, statSync import { tmpdir } from 'node:os'; import { join, resolve, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -144,7 +145,7 @@ function runCli(argv: string[], cwd: string, env: Record): Promi execFile( TSX, [CLI, ...argv], - { cwd, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1', ...env } }, + { cwd, maxBuffer: 32 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', ...env }) }, (err, stdout, stderr) => { resolvePromise({ code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0, diff --git a/packages/cli/test/login-json-ndjson.e2e.test.ts b/packages/cli/test/login-json-ndjson.e2e.test.ts index 9538e77eb9..0f835c9cf0 100644 --- a/packages/cli/test/login-json-ndjson.e2e.test.ts +++ b/packages/cli/test/login-json-ndjson.e2e.test.ts @@ -66,6 +66,7 @@ import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -267,7 +268,7 @@ async function runDeviceLogin(outcome: 'token' | 'access_denied'): Promise res(err ? Number((err as { code?: unknown }).code ?? 1) : 0), ); }); diff --git a/packages/cli/test/login-json-noninteractive.e2e.test.ts b/packages/cli/test/login-json-noninteractive.e2e.test.ts index c0fb31f7f2..18809de8ed 100644 --- a/packages/cli/test/login-json-noninteractive.e2e.test.ts +++ b/packages/cli/test/login-json-noninteractive.e2e.test.ts @@ -56,6 +56,7 @@ import { mkdtempSync, rmSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -99,7 +100,7 @@ function runLogin(args: string[], answer: Array<{ when: RegExp; send: string }> return new Promise((done) => { const child = spawn(TSX, [CLI, 'login', ...args], { stdio: ['pipe', 'pipe', 'pipe'], - env: { ...process.env, HOME: home, NO_COLOR: '1' }, + env: childEnv({ HOME: home, NO_COLOR: '1' }), }); let stdout = ''; diff --git a/packages/cli/test/migrate-exit-code.e2e.test.ts b/packages/cli/test/migrate-exit-code.e2e.test.ts index 64fab3293c..8ccb6c113c 100644 --- a/packages/cli/test/migrate-exit-code.e2e.test.ts +++ b/packages/cli/test/migrate-exit-code.e2e.test.ts @@ -36,6 +36,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -80,7 +81,7 @@ function runCli(args: string[], cwd: string, env: Record = {}): execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1', ...env } }, + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', ...env }) }, (err, stdout, stderr) => { resolvePromise({ // `err.code` is the real exit status; `null`/undefined means the diff --git a/packages/cli/test/migrate-meta.e2e.test.ts b/packages/cli/test/migrate-meta.e2e.test.ts index 67d66cc106..b9aa8ed302 100644 --- a/packages/cli/test/migrate-meta.e2e.test.ts +++ b/packages/cli/test/migrate-meta.e2e.test.ts @@ -23,6 +23,7 @@ import { dirname, join, resolve } from 'node:path'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; import { ObjectStackDefinitionSchema } from '@objectstack/spec'; +import { childEnv } from './helpers/serve-process.js'; const execFileP = promisify(execFile); const HERE = resolve(fileURLToPath(import.meta.url), '..'); @@ -156,7 +157,7 @@ async function runMeta(args: string[], cwd: string) { const { stdout } = await execFileP(TSX, [CLI, 'migrate', 'meta', ...args], { cwd, maxBuffer: 16 * 1024 * 1024, - env: { ...process.env, NO_COLOR: '1' }, + env: childEnv({ NO_COLOR: '1' }), }); return stdout; } @@ -431,7 +432,7 @@ export default defineStack({ await execFileP(TSX, [CLI, 'validate'], { cwd: rkDir, maxBuffer: 16 * 1024 * 1024, - env: { ...process.env, NO_COLOR: '1' }, + env: childEnv({ NO_COLOR: '1' }), }); } catch (e: any) { refused = true; diff --git a/packages/cli/test/qa-empty-glob-exit-code.e2e.test.ts b/packages/cli/test/qa-empty-glob-exit-code.e2e.test.ts index e47285b576..87658e56ae 100644 --- a/packages/cli/test/qa-empty-glob-exit-code.e2e.test.ts +++ b/packages/cli/test/qa-empty-glob-exit-code.e2e.test.ts @@ -38,6 +38,7 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -60,7 +61,7 @@ function runCli(args: string[], cwd: string): Promise { execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 8 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ // `err.code` is the real exit status; `null`/undefined means the child diff --git a/packages/cli/test/validate-json-warning-parity.e2e.test.ts b/packages/cli/test/validate-json-warning-parity.e2e.test.ts index 0409c5d086..89b7d29226 100644 --- a/packages/cli/test/validate-json-warning-parity.e2e.test.ts +++ b/packages/cli/test/validate-json-warning-parity.e2e.test.ts @@ -62,6 +62,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -145,7 +146,7 @@ function runCli(args: string[], cwd: string): Promise { execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0, diff --git a/packages/cli/test/validate-top-level-strict.e2e.test.ts b/packages/cli/test/validate-top-level-strict.e2e.test.ts index 49a92f94bc..e1d6cdffc0 100644 --- a/packages/cli/test/validate-top-level-strict.e2e.test.ts +++ b/packages/cli/test/validate-top-level-strict.e2e.test.ts @@ -30,6 +30,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { childEnv } from './helpers/serve-process.js'; const HERE = resolve(fileURLToPath(import.meta.url), '..'); const CLI = resolve(HERE, '../bin/run-dev.js'); @@ -74,7 +75,7 @@ function runCli(args: string[], cwd: string): Promise { execFile( TSX, [CLI, ...args], - { cwd, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, NO_COLOR: '1' } }, + { cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) }, (err, stdout, stderr) => { resolvePromise({ code: err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as unknown as { code: number }).code : 1) : 0, diff --git a/scripts/cli-test-child-env.baseline.json b/scripts/cli-test-child-env.baseline.json index 32e50bd861..ef87928123 100644 --- a/scripts/cli-test-child-env.baseline.json +++ b/scripts/cli-test-child-env.baseline.json @@ -23,21 +23,7 @@ "Sites that copy the whole environment ON PURPOSE do not belong here. They are DELIBERATE", "entries in the gate itself, carrying their reason, and they are pinned in both directions." ], - "packages/cli/test/cloud-login-json-ndjson.e2e.test.ts": 1, - "packages/cli/test/generate-agent-retired.e2e.test.ts": 1, - "packages/cli/test/generate-skill.e2e.test.ts": 1, - "packages/cli/test/hook-body-build-reach.e2e.test.ts": 1, - "packages/cli/test/init-created-files-summary.e2e.test.ts": 1, - "packages/cli/test/invocation-loudness.e2e.test.ts": 1, - "packages/cli/test/json-stdout-purity.e2e.test.ts": 1, - "packages/cli/test/login-json-ndjson.e2e.test.ts": 1, - "packages/cli/test/login-json-noninteractive.e2e.test.ts": 1, - "packages/cli/test/migrate-exit-code.e2e.test.ts": 1, - "packages/cli/test/migrate-meta.e2e.test.ts": 2, - "packages/cli/test/qa-empty-glob-exit-code.e2e.test.ts": 1, "packages/cli/test/serve-app-anchored-optional-import.e2e.test.ts": 1, "packages/cli/test/serve-app-runtime-hooks.e2e.test.ts": 1, - "packages/cli/test/serve-host-fallback-base.e2e.test.ts": 1, - "packages/cli/test/validate-json-warning-parity.e2e.test.ts": 1, - "packages/cli/test/validate-top-level-strict.e2e.test.ts": 1 + "packages/cli/test/serve-host-fallback-base.e2e.test.ts": 1 }