fix(#2665): scrub config-location env on raw spawns that sandbox only HOME
Same class as the in-process leak, on the child-spawn side. These call spawnSync
directly rather than through runGsdTools, so TEST_ENV_BASE never applies to them
and an ambient config-location var survives into the child.
- install-runtime-artifacts: the dev-preferences writer resolved env-first and
wrote SKILL.md into the live config dir instead of its tmp HOME. This also
fixes a test that FAILS today on any machine with CLAUDE_CONFIG_DIR set.
- issue-766: the `claude` CLI is third-party and bootstraps its own config into
whatever CLAUDE_CONFIG_DIR names, so even a bare --version probe wrote there.
It gets an explicit throwaway dir rather than a blank value -- GSD's resolvers
treat '' as falsy and fall back to the home dir, but a third-party binary
offers no such guarantee (blanking it produced a stray backups/ in the repo
root).
These two were the last writers standing between the suite and #2665's stated
acceptance criterion.
This commit is contained in:
@@ -4646,7 +4646,7 @@ const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const { cleanup, TEST_ENV_BASE } = require('./helpers.cjs');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const PROFILE_OUTPUT = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'profile-output.cjs');
|
||||
@@ -4672,7 +4672,12 @@ describe('Bug #2973: dev-preferences default writer path is skills/gsd-dev-prefe
|
||||
m.cmdGenerateDevPreferences(${JSON.stringify(tmpHome)}, { analysis: ${JSON.stringify(analysisPath)} }, false);
|
||||
`);
|
||||
const result = cp.spawnSync(process.execPath, [driver], {
|
||||
env: Object.assign({}, process.env, { HOME: tmpHome, USERPROFILE: tmpHome }),
|
||||
// #2665: TEST_ENV_BASE must be merged in explicitly here. This is a RAW
|
||||
// spawn, not runGsdTools, so nothing scrubs the config-location vars for
|
||||
// it -- and the writer under test resolves them env-FIRST. Sandboxing
|
||||
// HOME alone let an ambient CLAUDE_CONFIG_DIR win, and the SKILL.md
|
||||
// landed in the developer's live config dir instead of tmpHome.
|
||||
env: Object.assign({}, process.env, TEST_ENV_BASE, { HOME: tmpHome, USERPROFILE: tmpHome }),
|
||||
encoding: 'utf-8',
|
||||
// Bound the subprocess so a regression that hangs the writer
|
||||
// (or the dispatcher) cannot deadlock CI (PR #3003 CR feedback).
|
||||
|
||||
@@ -24,7 +24,7 @@ const ROOT = path.resolve(__dirname, '..');
|
||||
const identity = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'package-identity.cjs'));
|
||||
const pkg = require(path.join(ROOT, 'package.json'));
|
||||
const { MANAGED_HOOKS } = require(path.join(ROOT, 'hooks', 'managed-hooks-registry.cjs'));
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const { cleanup, TEST_ENV_BASE } = require('./helpers.cjs');
|
||||
|
||||
const PLUGIN_JSON_PATH = path.join(ROOT, '.claude-plugin', 'plugin.json');
|
||||
const HOOKS_JSON_PATH = path.join(ROOT, 'hooks', 'hooks.json');
|
||||
@@ -359,9 +359,36 @@ describe('C: plugin.json schema validation', () => {
|
||||
|
||||
// ── C2: Opportunistic CLI integration (skipped when claude not on PATH) ──────
|
||||
|
||||
// #2665: the `claude` CLI is a THIRD-PARTY binary that bootstraps its own
|
||||
// config (.claude.json plus a backups/ dir) into whatever CLAUDE_CONFIG_DIR
|
||||
// names. Two things follow, and only the first is obvious:
|
||||
//
|
||||
// 1. Inheriting the developer's ambient CLAUDE_CONFIG_DIR makes even a bare
|
||||
// `--version` probe write into their live config dir. That alone kept
|
||||
// `CLAUDE_CONFIG_DIR=<dir> npm run test:unit; find <dir> -type f` from
|
||||
// returning empty after every GSD-side leak was closed.
|
||||
// 2. BLANKING it (TEST_ENV_BASE's '' convention) is not enough here. GSD's
|
||||
// own resolvers treat '' as falsy and fall back to the home dir, but this
|
||||
// binary is not ours and gives no such guarantee -- blanking it produced a
|
||||
// stray backups/ directory in the REPO ROOT.
|
||||
//
|
||||
// So point it at a real throwaway dir rather than at nothing.
|
||||
const claudeCliHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-2665-claude-cli-'));
|
||||
const claudeCliEnv = () => ({
|
||||
...process.env,
|
||||
...TEST_ENV_BASE,
|
||||
HOME: claudeCliHome,
|
||||
USERPROFILE: claudeCliHome,
|
||||
CLAUDE_CONFIG_DIR: path.join(claudeCliHome, '.claude'),
|
||||
});
|
||||
|
||||
const claudeAvailable = (() => {
|
||||
try {
|
||||
const result = spawnSync('claude', ['--version'], { encoding: 'utf-8', timeout: 5000 });
|
||||
const result = spawnSync('claude', ['--version'], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
env: claudeCliEnv(),
|
||||
});
|
||||
return result.status === 0;
|
||||
} catch (_) {
|
||||
return false;
|
||||
@@ -384,6 +411,7 @@ describe('C: plugin.json schema validation', () => {
|
||||
cwd: ROOT,
|
||||
encoding: 'utf-8',
|
||||
timeout: 15000,
|
||||
env: claudeCliEnv(),
|
||||
});
|
||||
assert.equal(
|
||||
result.status,
|
||||
|
||||
Reference in New Issue
Block a user