fix(tests): the suite no longer hands the session id to the lock CLI
npm test now preloads tests/helpers/no-session-id.mjs, which deletes CLAUDE_CODE_SESSION_ID before any test file loads, and the 18 files that spread process.env into a child use childEnv() instead (23 sites). A live session's stop guard is no longer pointed at a temp-repo lock (S133). engines stays >=18: --import exists from Node 18.18/20.6, and the quoted test glob already needs Node 21 (criteria-runner NODE_GLOB_MAJOR), so the real floor does not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a97efb6989
commit
d8527976ef
22 changed files with 285 additions and 24 deletions
|
|
@ -18,6 +18,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, chmodSync, existsSync, s
|
|||
import { join, dirname } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const RUNNER = join(HERE, '..', '..', 'lib', 'verification', 'arch-rules.mjs');
|
||||
|
|
@ -67,7 +68,7 @@ function jsRepo({ cycle = false, binary = true } = {}) {
|
|||
}
|
||||
|
||||
function run(args, env = {}) {
|
||||
const r = spawnSync('node', [RUNNER, ...args], { encoding: 'utf-8', env: { ...process.env, ...env } });
|
||||
const r = spawnSync('node', [RUNNER, ...args], { encoding: 'utf-8', env: childEnv({ ...env }) });
|
||||
let json = null;
|
||||
try { json = JSON.parse(r.stdout); } catch { /* asserted by caller */ }
|
||||
return { code: r.status, json, out: r.stdout, err: r.stderr };
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { recordVerdict, planHash, acceptOpen, gatePlan } from '../../lib/validat
|
|||
import { stampBriefReview, checkBriefReview } from '../../lib/validators/brief-review-stamp.mjs';
|
||||
import { stampIntentApproval } from '../../lib/validators/intent-approval.mjs';
|
||||
import { buildPlan } from '../helpers/plan-v18-builder.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
|
|
@ -30,11 +31,11 @@ function lockRepo(withCmd) {
|
|||
writeFileSync(join(repo, 'tests', 'a.test.mjs'), "import { test } from 'node:test';\nimport assert from 'node:assert';\ntest('a', () => assert.equal(1, 2));\n");
|
||||
const args = ['lock', '--files', 'tests/a.test.mjs', '--step', '1'];
|
||||
if (withCmd) args.push('--cmd', 'node --test tests/a.test.mjs');
|
||||
const r = spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: { ...process.env, CLAUDE_CODE_SESSION_ID: SESS } });
|
||||
const r = spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: childEnv({ CLAUDE_CODE_SESSION_ID: SESS }) });
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
return repo;
|
||||
}
|
||||
const lockCli = (repo, args) => spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: { ...process.env, CLAUDE_CODE_SESSION_ID: SESS } });
|
||||
const lockCli = (repo, args) => spawnSync(process.execPath, [LOCK, ...args], { cwd: repo, encoding: 'utf8', env: childEnv({ CLAUDE_CODE_SESSION_ID: SESS }) });
|
||||
|
||||
test('green: another allowed command (git status) can never mark a red step green', async () => {
|
||||
const repo = lockRepo(true);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
MAX_CONV_TURNS,
|
||||
MAX_TOTAL_DIMENSIONS,
|
||||
} from '../../lib/util/research-loop-cap.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const SHIM = join(HERE, '..', '..', 'lib', 'util', 'research-loop-cap.mjs');
|
||||
|
|
@ -40,7 +41,7 @@ function runShim(args, env) {
|
|||
const out = execFileSync(process.execPath, [SHIM, ...args], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, ...env },
|
||||
env: childEnv({ ...env }),
|
||||
});
|
||||
return { code: 0, out };
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { stepTestsOnly } from '../../lib/parsers/plan-schema.mjs';
|
|||
import { validateFindings } from '../../lib/review/findings-schema.mjs';
|
||||
import { lintSkill } from '../../lib/skills/skill-lint.mjs';
|
||||
import * as routing from '../../lib/skills/routing.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const awaitImportSync = () => routing;
|
||||
|
||||
|
|
@ -980,7 +981,7 @@ test('review 630 r8 / red-proof.mjs:101: a run reports one reading of a stop, no
|
|||
writeFileSync(join(repo, 'tests', 'a.test.mjs'), 'x\n');
|
||||
const out = spawnSync(process.execPath, [join(ROOT, 'lib', 'verification', 'red-proof.mjs'), '--cwd', repo, '--base', base,
|
||||
'--cmd', 'jest tests/a.test.mjs', '--tests', 'tests/a.test.mjs', '--red-only', '--json'],
|
||||
{ encoding: 'utf8', env: { ...process.env, PATH: bin.path }, timeout: 60_000 });
|
||||
{ encoding: 'utf8', env: childEnv({ PATH: bin.path }), timeout: 60_000 });
|
||||
const r = JSON.parse(out.stdout);
|
||||
assert.equal(r.status, 'not-run', JSON.stringify(r));
|
||||
assert.match(r.reason, /could not start/, r.reason);
|
||||
|
|
|
|||
217
tests/lib/session-id-isolation.test.mjs
Normal file
217
tests/lib/session-id-isolation.test.mjs
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// tests/lib/session-id-isolation.test.mjs
|
||||
// The test suite must not run under the live session's id. The lock CLI
|
||||
// (lib/util/test-lock.mjs) writes a session pointer keyed by
|
||||
// CLAUDE_CODE_SESSION_ID into <tmpdir>/voyage-test-locks-<uid>/, and the stop
|
||||
// guard reads it; a test child that inherits the id of the session running
|
||||
// `npm test` writes a pointer for that live session (S133). Two layers keep
|
||||
// the id out:
|
||||
// - `npm test` preloads tests/helpers/no-session-id.mjs, which deletes the
|
||||
// id from the runner's environment before any test file starts;
|
||||
// - every test that starts a child builds the child's environment with
|
||||
// childEnv() (tests/helpers/child-env.mjs), which drops the id, so a
|
||||
// single file run directly (`node --test <file>`) does not leak it either.
|
||||
//
|
||||
// Parts:
|
||||
// (a) static scan: no file under tests/ spreads the process environment
|
||||
// into a child, except the helper itself;
|
||||
// (b) childEnv() drops an inherited id and keeps an id the caller sets;
|
||||
// (c) the test command holds the preload, and the preload clears the id;
|
||||
// (d) every test file that drives the lock CLI or the stop guard runs green,
|
||||
// through the test command's preload and directly, and leaves no
|
||||
// pointer for a made-up id;
|
||||
// (e) known-positive control: the lock CLI run with that id does write the
|
||||
// pointer at the computed path, so (d) cannot pass on a wrong path.
|
||||
//
|
||||
// Every child environment here is built without a spread (Object.entries +
|
||||
// filter), so this file does not depend on the helper and does not trip (a).
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync, readdirSync, existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from 'node:fs';
|
||||
import { join, dirname, relative, sep } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { makeRepo } from '../hooks/guard-helpers.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const SELF = 'tests/lib/session-id-isolation.test.mjs';
|
||||
const HELPER = 'tests/helpers/child-env.mjs';
|
||||
const PRELOAD = 'tests/helpers/no-session-id.mjs';
|
||||
const PRELOAD_FLAG = `--import ./${PRELOAD}`;
|
||||
const LOCK_CLI = join(ROOT, 'lib', 'util', 'test-lock.mjs');
|
||||
const ID_VAR = 'CLAUDE_CODE_SESSION_ID';
|
||||
const TIMEOUT = 300_000;
|
||||
|
||||
// Built from parts so this file's own text never matches them.
|
||||
const PENV = 'process' + '\\.env(?![\\w$.])';
|
||||
const SPREAD_PATTERNS = [
|
||||
['spread', new RegExp('\\.\\.\\.\\s*' + PENV)],
|
||||
['env-property', new RegExp('\\benv\\s*:\\s*' + PENV)],
|
||||
['object-assign', new RegExp('Object\\.assign\\([^)]*' + PENV)],
|
||||
];
|
||||
|
||||
function walk(dir, keep) {
|
||||
const out = [];
|
||||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = join(dir, e.name);
|
||||
if (e.isDirectory()) out.push(...walk(p, keep));
|
||||
else if (keep(e.name)) out.push(relative(ROOT, p).split(sep).join('/'));
|
||||
}
|
||||
return out.sort();
|
||||
}
|
||||
|
||||
const madeUpId = () => `isolation-${randomUUID()}`;
|
||||
|
||||
// The pointer path, as lib/util/test-lock.mjs:76-78 computes it.
|
||||
function pointerPath(tmp, id) {
|
||||
const uid = process.getuid ? process.getuid() : 'u';
|
||||
const name = `${createHash('sha256').update(String(id)).digest('hex').slice(0, 32)}.json`;
|
||||
return join(tmp, `voyage-test-locks-${uid}`, name);
|
||||
}
|
||||
|
||||
// The child's environment: ours without NODE_TEST_CONTEXT (so a nested
|
||||
// `node --test` reports its own exit code) and without the live id, plus a
|
||||
// made-up id and a private TMPDIR.
|
||||
function isolatedEnv(id, tmp) {
|
||||
const env = Object.fromEntries(Object.entries(process.env)
|
||||
.filter(([k]) => k !== 'NODE_TEST_CONTEXT' && k !== ID_VAR));
|
||||
env[ID_VAR] = id;
|
||||
env.TMPDIR = tmp;
|
||||
return env;
|
||||
}
|
||||
|
||||
const privateTmp = () => realpathSync(mkdtempSync(join(tmpdir(), 'voyage-sid-')));
|
||||
|
||||
function testScript() {
|
||||
return JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).scripts?.test ?? '';
|
||||
}
|
||||
|
||||
// The node flags the test command puts before --test (the preload).
|
||||
function preloadFlags() {
|
||||
const words = testScript().split(/\s+/).filter(Boolean);
|
||||
const start = words.indexOf('node');
|
||||
const end = words.indexOf('--test');
|
||||
if (start < 0 || end < 0 || end < start) return [];
|
||||
return words.slice(start + 1, end);
|
||||
}
|
||||
|
||||
test('(a) no test file spreads the process environment into a child, except the helper', () => {
|
||||
const files = walk(join(ROOT, 'tests'), (n) => n.endsWith('.mjs'));
|
||||
assert.ok(files.length >= 140, `the scan saw only ${files.length} .mjs files under tests/; an empty scan proves nothing`);
|
||||
const hits = [];
|
||||
for (const rel of files) {
|
||||
if (rel === HELPER) continue;
|
||||
const text = readFileSync(join(ROOT, rel), 'utf8');
|
||||
for (const [kind, re] of SPREAD_PATTERNS) {
|
||||
if (re.test(text)) hits.push(`${rel} (${kind})`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(hits, [], `use childEnv() from ${HELPER} instead:\n${hits.join('\n')}`);
|
||||
});
|
||||
|
||||
test('(b) childEnv() drops an inherited id and keeps an id the caller sets', async () => {
|
||||
let mod;
|
||||
try {
|
||||
mod = await import(pathToFileURL(join(ROOT, HELPER)).href);
|
||||
} catch (e) {
|
||||
// Only the error's name: its message would read as a load crash to red-proof.
|
||||
assert.fail(`child-env helper missing: ${HELPER} could not be loaded (${e.name})`);
|
||||
}
|
||||
assert.equal(typeof mod.childEnv, 'function', 'child-env helper missing: no childEnv export');
|
||||
const had = Object.prototype.hasOwnProperty.call(process.env, ID_VAR);
|
||||
const saved = process.env[ID_VAR];
|
||||
const inherited = madeUpId();
|
||||
process.env[ID_VAR] = inherited;
|
||||
try {
|
||||
const plain = mod.childEnv();
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(plain, ID_VAR), false, 'an inherited id must not reach the child');
|
||||
assert.equal(plain.PATH, process.env.PATH, 'the rest of the environment is kept');
|
||||
plain.VOYAGE_SID_PROBE = 'x';
|
||||
assert.equal(process.env.VOYAGE_SID_PROBE, undefined, 'childEnv() returns a copy, not process.env');
|
||||
|
||||
const own = mod.childEnv({ [ID_VAR]: 'sess-own', VOYAGE_SID_EXTRA: 'y' });
|
||||
assert.equal(own[ID_VAR], 'sess-own', 'an id the test sets itself is kept');
|
||||
assert.equal(own.VOYAGE_SID_EXTRA, 'y', 'overrides are applied');
|
||||
assert.equal(mod.childEnv({ PATH: '/usr/bin:/bin' }).PATH, '/usr/bin:/bin', 'overrides win over the inherited value');
|
||||
} finally {
|
||||
if (had) process.env[ID_VAR] = saved;
|
||||
else delete process.env[ID_VAR];
|
||||
}
|
||||
});
|
||||
|
||||
test('(c) the test command preloads the helper, and the preload clears the id', () => {
|
||||
assert.ok(testScript().includes(PRELOAD_FLAG),
|
||||
`package.json scripts.test must hold "${PRELOAD_FLAG}"; it is: ${testScript()}`);
|
||||
const tmp = privateTmp();
|
||||
try {
|
||||
const r = spawnSync(process.execPath,
|
||||
['--import', `./${PRELOAD}`, '-e', `process.stdout.write(JSON.stringify(process.env.${ID_VAR} ?? null))`],
|
||||
{ cwd: ROOT, encoding: 'utf8', env: isolatedEnv(madeUpId(), tmp) });
|
||||
assert.equal(r.status, 0, `the preload child did not run: ${r.stderr}`);
|
||||
assert.equal(r.stdout.trim(), 'null', 'the preload must delete the id');
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Files that name the lock CLI or the stop guard but do not drive either.
|
||||
// Each must still exist and still match, so the list stays honest.
|
||||
const NOT_DRIVERS = new Map([
|
||||
['tests/lib/review-630.test.mjs', 'names the lock CLI only in source-reading assertions (:502, :952-964)'],
|
||||
['tests/lib/skills-workshop-prose-pins.test.mjs', 'prose pin: checks the skill names the CLI (:85)'],
|
||||
]);
|
||||
const DRIVER_RE = /test-lock\.mjs|stop-test-lock/;
|
||||
|
||||
test('(d) the lock-driving test files run green and leave no pointer for a made-up id', { timeout: TIMEOUT }, () => {
|
||||
const all = walk(join(ROOT, 'tests'), (n) => n.endsWith('.test.mjs')).filter((f) => f !== SELF);
|
||||
const matching = all.filter((rel) => DRIVER_RE.test(readFileSync(join(ROOT, rel), 'utf8')));
|
||||
for (const [rel, why] of NOT_DRIVERS) {
|
||||
assert.ok(existsSync(join(ROOT, rel)), `excluded file is gone, drop it from the list: ${rel} (${why})`);
|
||||
assert.ok(matching.includes(rel), `excluded file no longer names the lock CLI, drop it from the list: ${rel}`);
|
||||
}
|
||||
const drivers = matching.filter((rel) => !NOT_DRIVERS.has(rel));
|
||||
assert.ok(drivers.length >= 5, `only ${drivers.length} lock-driving test files found: ${drivers.join(', ')}`);
|
||||
|
||||
const flags = preloadFlags();
|
||||
const problems = [];
|
||||
for (const rel of drivers) {
|
||||
for (const [mode, pre] of [['test command', flags], ['direct', []]]) {
|
||||
const id = madeUpId();
|
||||
const tmp = privateTmp();
|
||||
try {
|
||||
const r = spawnSync(process.execPath, [...pre, '--test', '--test-reporter=tap', rel],
|
||||
{ cwd: ROOT, encoding: 'utf8', env: isolatedEnv(id, tmp), timeout: TIMEOUT });
|
||||
const out = `${r.stdout || ''}\n${r.stderr || ''}`;
|
||||
const m = out.match(/^# tests (\d+)$/m);
|
||||
if (r.status !== 0) problems.push(`${rel} [${mode}]: exit ${r.status}${r.signal ? ` (${r.signal})` : ''}`);
|
||||
if (!m || Number(m[1]) === 0) problems.push(`${rel} [${mode}]: reported no tests`);
|
||||
if (existsSync(pointerPath(tmp, id))) problems.push(`${rel} [${mode}]: left a session pointer for the made-up id`);
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.deepEqual(problems, []);
|
||||
});
|
||||
|
||||
test('(e) known-positive control: the lock CLI writes the pointer at the computed path', () => {
|
||||
const id = madeUpId();
|
||||
const tmp = privateTmp();
|
||||
const repo = makeRepo('main');
|
||||
try {
|
||||
mkdirSync(join(repo, 'tests'), { recursive: true });
|
||||
writeFileSync(join(repo, 'tests', 'a.test.js'), 'a\n');
|
||||
const r = spawnSync(process.execPath, [LOCK_CLI, 'lock', '--files', 'tests/a.test.js', '--step', '1'],
|
||||
{ cwd: repo, encoding: 'utf8', env: isolatedEnv(id, tmp) });
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
const p = pointerPath(tmp, id);
|
||||
assert.ok(existsSync(p), `the lock CLI wrote no pointer at ${p}; the path in (d) would be wrong`);
|
||||
assert.ok(existsSync(JSON.parse(readFileSync(p, 'utf8')).lock_path), 'the pointer names the lock file');
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
rmSync(repo, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
@ -27,6 +27,7 @@ import { fileURLToPath } from 'node:url';
|
|||
import {
|
||||
parseExecutionStrategy, buildWaveOrders, progressFileFor, waveStatus,
|
||||
} from '../../lib/util/session-orders.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
|
|
@ -100,7 +101,7 @@ function fill(script, values) {
|
|||
|
||||
const BASH = existsSync('/bin/bash') ? '/bin/bash' : 'bash';
|
||||
function runBash(script, cwd, env = {}) {
|
||||
return spawnSync(BASH, ['-c', script], { cwd, encoding: 'utf-8', env: { ...process.env, ...env } });
|
||||
return spawnSync(BASH, ['-c', script], { cwd, encoding: 'utf-8', env: childEnv({ ...env }) });
|
||||
}
|
||||
|
||||
function gitc(cwd, ...args) {
|
||||
|
|
@ -204,7 +205,7 @@ test('CLI on the fixture: wave 1 writes two orders and wave.json, prints the sta
|
|||
|
||||
const sessionDir = join(root, '.claude', 'trekplan-sessions', 'demo');
|
||||
const r = spawnSync(process.execPath, [MODULE, 'write', '--plan', PLAN_REL, '--wave', '1', '--session-dir', sessionDir], {
|
||||
cwd: root, encoding: 'utf-8', env: { ...process.env, PATH: `${bin}:${process.env.PATH}` },
|
||||
cwd: root, encoding: 'utf-8', env: childEnv({ PATH: `${bin}:${process.env.PATH}` }),
|
||||
});
|
||||
assert.equal(r.status, 0, r.stderr);
|
||||
assert.equal(existsSync(marker), false, 'nothing may start Claude Code');
|
||||
|
|
@ -555,7 +556,7 @@ test('Check 4 and Step 5 pass a first run (no worktrees yet) under zsh as well a
|
|||
const step5 = fill(bashBlock('### Step 5 — Cleanup'), { slug: 'demo' });
|
||||
for (const shell of [BASH, ZSH]) {
|
||||
for (const [name, block] of [['Check 4', check4], ['Step 5', step5]]) {
|
||||
const r = spawnSync(shell, ['-c', block], { cwd: root, encoding: 'utf-8', env: { ...process.env, ...env } });
|
||||
const r = spawnSync(shell, ['-c', block], { cwd: root, encoding: 'utf-8', env: childEnv({ ...env }) });
|
||||
assert.equal(r.status, 0, `${name} under ${shell}: ${r.stderr}`);
|
||||
assert.doesNotMatch(r.stderr, /no matches found/, `${name} under ${shell}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { tmpdir } from 'node:os';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { emit, buildRecord, resolveStatsPath, KNOWN_EVENTS } from '../../lib/stats/event-emit.mjs';
|
||||
import { resolveStatsDir } from '../../lib/stats/stats-append.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const SHIM = join(HERE, '..', '..', 'lib', 'stats', 'event-emit.mjs');
|
||||
|
|
@ -36,7 +37,7 @@ const DATA_REL = join('.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketpla
|
|||
|
||||
/** process.env with HOME pinned and CLAUDE_PLUGIN_DATA empty: never the real data dir. */
|
||||
function isolatedEnv(home) {
|
||||
const env = { ...process.env, CLAUDE_PLUGIN_DATA: '', HOME: home };
|
||||
const env = childEnv({ CLAUDE_PLUGIN_DATA: '', HOME: home });
|
||||
delete env.CLAUDE_COORD_DIR;
|
||||
return env;
|
||||
}
|
||||
|
|
@ -169,7 +170,7 @@ test('CLI shim writes via emit when CLAUDE_PLUGIN_DATA is set', () => {
|
|||
execFileSync(process.execPath, [
|
||||
SHIM, '--event', 'brief-approved', '--payload', '{"foo":42}',
|
||||
], {
|
||||
env: { ...process.env, CLAUDE_PLUGIN_DATA: dir },
|
||||
env: childEnv({ CLAUDE_PLUGIN_DATA: dir }),
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const path = join(dir, 'trekexecute-stats.jsonl');
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symli
|
|||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
|
|
@ -48,7 +49,7 @@ function appendLine(sec, kind, rel) {
|
|||
|
||||
/** process.env without CLAUDE_COORD_DIR: a test never reads the real coord queue. */
|
||||
function baseEnv() {
|
||||
const env = { ...process.env };
|
||||
const env = childEnv();
|
||||
delete env.CLAUDE_COORD_DIR;
|
||||
return env;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import { spawnSync } from 'node:child_process';
|
|||
import { writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { ROOT, makeRepo, gitIn, fakeTool, tmp, pluginCopy } from '../hooks/guard-helpers.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const CLI = join(ROOT, 'lib', 'verification', 'step-check.mjs');
|
||||
|
||||
function run(cli, args, env = {}) {
|
||||
const r = spawnSync(process.execPath, [cli, ...args], { encoding: 'utf8', env: { ...process.env, PATH: '/usr/bin:/bin', ...env } });
|
||||
const r = spawnSync(process.execPath, [cli, ...args], { encoding: 'utf8', env: childEnv({ PATH: '/usr/bin:/bin', ...env }) });
|
||||
let json = null;
|
||||
try { json = JSON.parse(r.stdout); } catch { json = null; }
|
||||
return { code: r.status, stdout: r.stdout, stderr: r.stderr, json };
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { makeRepo, gitIn, tmp } from '../hooks/guard-helpers.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const CLI = join(HERE, '..', '..', 'lib', 'util', 'test-lock.mjs');
|
||||
|
||||
function run(cwd, args, env = {}) {
|
||||
const r = spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: { ...process.env, ...env } });
|
||||
const r = spawnSync(process.execPath, [CLI, ...args], { cwd, encoding: 'utf8', env: childEnv({ ...env }) });
|
||||
return { code: r.status, stdout: r.stdout, stderr: r.stderr };
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue