diff --git a/packages/capture-kit/src/recording/__tests__/overlay.test.ts b/packages/capture-kit/src/recording/__tests__/overlay.test.ts index 47b1b34180..ec2a56cbee 100644 --- a/packages/capture-kit/src/recording/__tests__/overlay.test.ts +++ b/packages/capture-kit/src/recording/__tests__/overlay.test.ts @@ -48,7 +48,6 @@ beforeEach(() => { }); afterEach(() => { - vi.unstubAllEnvs(); fs.rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/packages/capture-kit/src/recording/swift-cache.test.ts b/packages/capture-kit/src/recording/swift-cache.test.ts index f04aea5913..8949f5583e 100644 --- a/packages/capture-kit/src/recording/swift-cache.test.ts +++ b/packages/capture-kit/src/recording/swift-cache.test.ts @@ -31,7 +31,6 @@ beforeEach(() => { }); afterEach(() => { - vi.unstubAllEnvs(); fs.rmSync(tmpDir, { recursive: true, force: true }); }); diff --git a/packages/maestro/src/daemon-port/__tests__/run-script-http.test.ts b/packages/maestro/src/daemon-port/__tests__/run-script-http.test.ts index a736c73d5d..4f121ee7fa 100644 --- a/packages/maestro/src/daemon-port/__tests__/run-script-http.test.ts +++ b/packages/maestro/src/daemon-port/__tests__/run-script-http.test.ts @@ -28,7 +28,6 @@ beforeEach(() => { }); afterEach(() => { - vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); diff --git a/packages/platform-android/src/__tests__/doctor.test.ts b/packages/platform-android/src/__tests__/doctor.test.ts index 32a372f7ed..22aea044ac 100644 --- a/packages/platform-android/src/__tests__/doctor.test.ts +++ b/packages/platform-android/src/__tests__/doctor.test.ts @@ -42,7 +42,6 @@ import type { DoctorCheck } from '@agent-device/contracts/observability'; afterEach(() => { resetAndroidTestImeActivationCacheForTests(); - vi.unstubAllEnvs(); }); function fakeAdb(currentIme: string, previousIme = 'null'): AndroidAdbExecutor { diff --git a/packages/platform-apple/src/runner/__tests__/runner-startup-transport.test.ts b/packages/platform-apple/src/runner/__tests__/runner-startup-transport.test.ts index 8e94fa5842..c5b2a27d21 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-startup-transport.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-startup-transport.test.ts @@ -57,7 +57,6 @@ beforeEach(() => { afterEach(() => { vi.unstubAllGlobals(); - vi.unstubAllEnvs(); }); test('waitForRunner propagates request cancellation without fallback', async () => { diff --git a/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts b/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts index b6af37284e..d15a8ef03a 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts @@ -96,7 +96,6 @@ beforeEach(() => { afterEach(() => { vi.unstubAllGlobals(); - vi.unstubAllEnvs(); }); test('sendRunnerCommandOnce does not retry or simulator fallback after request failure', async () => { diff --git a/packages/platform-web/src/agent-browser-tool.test.ts b/packages/platform-web/src/agent-browser-tool.test.ts index fbc65051ff..0a97d854f7 100644 --- a/packages/platform-web/src/agent-browser-tool.test.ts +++ b/packages/platform-web/src/agent-browser-tool.test.ts @@ -209,7 +209,6 @@ for (const scenario of [ 'agent-browser@0.27.1', ]); } finally { - vi.unstubAllEnvs(); fs.rmSync(stateDir, { recursive: true, force: true }); } }); @@ -235,7 +234,6 @@ test('managed agent-browser setup reports an install that produced no entry', as ); }); } finally { - vi.unstubAllEnvs(); fs.rmSync(stateDir, { recursive: true, force: true }); } }); @@ -261,7 +259,6 @@ test('managed agent-browser setup gives the install lock back on every path out' ); }); } finally { - vi.unstubAllEnvs(); assert.deepEqual(lockPathsUnder(stateDir), []); fs.rmSync(stateDir, { recursive: true, force: true }); } diff --git a/packages/provision-kit/src/install-source-network-transport.test.ts b/packages/provision-kit/src/install-source-network-transport.test.ts index 3b219119d8..e3616b7e11 100644 --- a/packages/provision-kit/src/install-source-network-transport.test.ts +++ b/packages/provision-kit/src/install-source-network-transport.test.ts @@ -66,6 +66,5 @@ test('direct requests connect to the approved address through the real lookup', } } finally { await new Promise((resolve) => server.close(() => resolve())); - vi.unstubAllEnvs(); } }); diff --git a/src/__tests__/client-metro-auto-companion.test.ts b/src/__tests__/client-metro-auto-companion.test.ts index 6c0613db50..5ce9ef3a69 100644 --- a/src/__tests__/client-metro-auto-companion.test.ts +++ b/src/__tests__/client-metro-auto-companion.test.ts @@ -21,7 +21,6 @@ afterEach(() => { vi.useRealTimers(); vi.clearAllMocks(); vi.restoreAllMocks(); - vi.unstubAllEnvs(); }); test('prepareMetroRuntime starts the local companion only after bridge setup needs it', async () => { diff --git a/src/__tests__/cloud-connect-auth.test.ts b/src/__tests__/cloud-connect-auth.test.ts index 8a35d800eb..08cbcc9c1f 100644 --- a/src/__tests__/cloud-connect-auth.test.ts +++ b/src/__tests__/cloud-connect-auth.test.ts @@ -9,7 +9,6 @@ import type { AgentDeviceClient } from '../agent-device-client.ts'; import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; afterEach(() => { - vi.unstubAllEnvs(); vi.unstubAllGlobals(); vi.restoreAllMocks(); }); diff --git a/src/__tests__/cloud-connect-profile.test.ts b/src/__tests__/cloud-connect-profile.test.ts index 9b27be59a9..fd25bb2551 100644 --- a/src/__tests__/cloud-connect-profile.test.ts +++ b/src/__tests__/cloud-connect-profile.test.ts @@ -35,7 +35,6 @@ vi.mock('../provider-webdriver.ts', () => ({ afterEach(() => { vi.clearAllMocks(); vi.unstubAllGlobals(); - vi.unstubAllEnvs(); }); const mockedResolveCloudAccessForConnect = vi.mocked(resolveCloudAccessForConnect); diff --git a/src/__tests__/remote-connection-platform-axis.test.ts b/src/__tests__/remote-connection-platform-axis.test.ts index 02b16927be..d81737ba0f 100644 --- a/src/__tests__/remote-connection-platform-axis.test.ts +++ b/src/__tests__/remote-connection-platform-axis.test.ts @@ -27,7 +27,6 @@ import { readRemoteConnectionState } from '../remote/remote-connection-state.ts' afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); - vi.unstubAllEnvs(); }); test('proxy install against an iOS-bound connection is not refused as a platform change', async () => { diff --git a/src/__tests__/remote-connection.test.ts b/src/__tests__/remote-connection.test.ts index c97a819e47..66d1e62727 100644 --- a/src/__tests__/remote-connection.test.ts +++ b/src/__tests__/remote-connection.test.ts @@ -48,7 +48,6 @@ import type { AgentDeviceClient } from '../agent-device-client.ts'; afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); - vi.unstubAllEnvs(); }); test('deferred Metro config ignores perf-style kind values', () => { diff --git a/src/__tests__/update-check.test.ts b/src/__tests__/update-check.test.ts index 58e2baf6b7..0c214f73b6 100644 --- a/src/__tests__/update-check.test.ts +++ b/src/__tests__/update-check.test.ts @@ -46,7 +46,6 @@ beforeEach(() => { afterEach(() => { vi.restoreAllMocks(); - vi.unstubAllEnvs(); vi.useRealTimers(); Object.defineProperty(process.stderr, 'isTTY', { configurable: true, diff --git a/src/cli/commands/__tests__/plugins.test.ts b/src/cli/commands/__tests__/plugins.test.ts index dab8e1b2fd..81825bd4ba 100644 --- a/src/cli/commands/__tests__/plugins.test.ts +++ b/src/cli/commands/__tests__/plugins.test.ts @@ -12,7 +12,6 @@ vi.mock('../../../provider-device-runtimes.ts', () => { afterEach(() => { vi.restoreAllMocks(); - vi.unstubAllEnvs(); }); test('plugins list and remove route through the CLI and emits JSON without daemon access or plugin evaluation', async () => { diff --git a/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts b/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts index 91f4da210a..4ad2f6e8c8 100644 --- a/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts +++ b/src/daemon-client/__tests__/daemon-client-lifecycle.test.ts @@ -59,7 +59,6 @@ afterEach(() => { mockRunCmdDetached.mockReset(); mockRunCmdSync.mockClear(); mockSleep.mockClear(); - vi.unstubAllEnvs(); }); function makeTempStateDir(prefix: string): string { @@ -503,7 +502,6 @@ test('sendToDaemon does not reuse reachable daemon metadata with mismatched vers await closeLoopbackServer(staleDaemon.server); await closeLoopbackServer(freshDaemon.server); fs.rmSync(stateDir, { recursive: true, force: true }); - vi.unstubAllEnvs(); } } }); diff --git a/src/daemon-client/__tests__/daemon-client-newer-daemon.test.ts b/src/daemon-client/__tests__/daemon-client-newer-daemon.test.ts index 4229f8cc79..368d7a4755 100644 --- a/src/daemon-client/__tests__/daemon-client-newer-daemon.test.ts +++ b/src/daemon-client/__tests__/daemon-client-newer-daemon.test.ts @@ -28,7 +28,6 @@ const mockRunCmdDetached = vi.mocked(runCmdDetachedMonitored); afterEach(() => { mockRunCmdDetached.mockReset(); - vi.unstubAllEnvs(); }); test('sendToDaemon refuses to replace a reachable daemon newer than the client', async (t) => { diff --git a/src/daemon-client/__tests__/daemon-client-policy-reuse.test.ts b/src/daemon-client/__tests__/daemon-client-policy-reuse.test.ts index 62f17014e8..f6e510494f 100644 --- a/src/daemon-client/__tests__/daemon-client-policy-reuse.test.ts +++ b/src/daemon-client/__tests__/daemon-client-policy-reuse.test.ts @@ -28,7 +28,6 @@ const mockSpawnDaemon = vi.mocked(runCmdDetachedMonitored); afterEach(() => { mockSpawnDaemon.mockReset(); - vi.unstubAllEnvs(); }); test('a caller naming a policy refuses a running daemon without that policy', async (t) => { diff --git a/src/daemon-client/__tests__/daemon-client-startup-race.test.ts b/src/daemon-client/__tests__/daemon-client-startup-race.test.ts index d52ba0e6c2..0a094c07ab 100644 --- a/src/daemon-client/__tests__/daemon-client-startup-race.test.ts +++ b/src/daemon-client/__tests__/daemon-client-startup-race.test.ts @@ -62,7 +62,6 @@ afterEach(() => { mockRunCmdDetached.mockReset(); mockSleep.mockReset(); mockSleep.mockImplementation(async () => {}); - vi.unstubAllEnvs(); }); /** The code signature this client stamps on, and expects of, a daemon it may reuse. */ diff --git a/src/mcp/__tests__/command-tools-operator-inputs.test.ts b/src/mcp/__tests__/command-tools-operator-inputs.test.ts index 91b2633b0d..58d1eae43f 100644 --- a/src/mcp/__tests__/command-tools-operator-inputs.test.ts +++ b/src/mcp/__tests__/command-tools-operator-inputs.test.ts @@ -220,26 +220,22 @@ test('MCP refuses any argument the advertised schema does not list', async () => test('MCP still resolves operator env values outside the model-writable surface', async () => { vi.stubEnv('AGENT_DEVICE_DAEMON_AUTH_TOKEN', 'operator-env-token'); vi.stubEnv('AGENT_DEVICE_STATE_DIR', '/operator/state-dir'); - try { - const createdConfigs: Array> = []; - const calls: Array<{ name: string; input: Record }> = []; - const executor = createCommandToolExecutor({ - createClient: (config) => { - createdConfigs.push(config as Record); - return {} as AgentDeviceClient; - }, - runCommand: async (_client, name, input) => { - calls.push({ name, input: input as Record }); - return {}; - }, - }); - - const result = await executor.execute('wait', {}); - - assert.equal(result.isError, false); - assert.equal(calls[0]?.input.daemonAuthToken, 'operator-env-token'); - assert.equal(createdConfigs[0]?.stateDir, '/operator/state-dir'); - } finally { - vi.unstubAllEnvs(); - } + const createdConfigs: Array> = []; + const calls: Array<{ name: string; input: Record }> = []; + const executor = createCommandToolExecutor({ + createClient: (config) => { + createdConfigs.push(config as Record); + return {} as AgentDeviceClient; + }, + runCommand: async (_client, name, input) => { + calls.push({ name, input: input as Record }); + return {}; + }, + }); + + const result = await executor.execute('wait', {}); + + assert.equal(result.isError, false); + assert.equal(calls[0]?.input.daemonAuthToken, 'operator-env-token'); + assert.equal(createdConfigs[0]?.stateDir, '/operator/state-dir'); }); diff --git a/src/mcp/__tests__/command-tools-parity.test.ts b/src/mcp/__tests__/command-tools-parity.test.ts index 113ffcc363..8c9de97e49 100644 --- a/src/mcp/__tests__/command-tools-parity.test.ts +++ b/src/mcp/__tests__/command-tools-parity.test.ts @@ -12,7 +12,6 @@ import { validateAgainstSchema } from './output-schema-validator.ts'; import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; afterEach(() => { - vi.unstubAllEnvs(); if (temporaryDirectory) { fs.rmSync(temporaryDirectory, { recursive: true, force: true }); temporaryDirectory = undefined; diff --git a/vitest.config.ts b/vitest.config.ts index 1b4390fc19..5e42d9810e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -58,6 +58,10 @@ export const MUTATION_EXCLUDED_TESTS: readonly string[] = [ ...FUZZ_WORKER_TESTS, ]; +// Env stubs never outlive the test that made them. Applied to every project below and imported by +// vitest.mutation.config.ts, so no lane can leave it out. +export const TEST_ISOLATION = { unstubEnvs: true } as const; + // Imported by vitest.mutation.config.ts so the two lanes cannot drift: a guard // added here must reach the Stryker sandbox too. export const SETUP_FILES = [ @@ -245,7 +249,7 @@ export default defineConfig({ setupFiles: SETUP_FILES, }, }, - ], + ].map((project) => ({ ...project, test: { ...TEST_ISOLATION, ...project.test } })), coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov', 'json-summary'], diff --git a/vitest.mutation.config.ts b/vitest.mutation.config.ts index c02d3136d4..24ab16fb98 100644 --- a/vitest.mutation.config.ts +++ b/vitest.mutation.config.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; import { readTestScope, threadHostileTestFiles } from './scripts/mutation/test-scope.ts'; import { workspaceSourceAliases } from './scripts/mutation/workspace-aliases.ts'; -import { MUTATION_EXCLUDED_TESTS, SETUP_FILES } from './vitest.config.ts'; +import { MUTATION_EXCLUDED_TESTS, SETUP_FILES, TEST_ISOLATION } from './vitest.config.ts'; const repoRoot = path.dirname(fileURLToPath(import.meta.url)); @@ -27,5 +27,6 @@ export default defineConfig({ '**/node_modules/**', ], setupFiles: [...SETUP_FILES], + ...TEST_ISOLATION, }, });