mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
test: live codex smokes run codex in a private HOME and CODEX_HOME
codex-resume-flag-semantics and codex-e2e-sol-scope probe the codex CLI (the latter at module load, in every free run), and every codex run writes $CODEX_HOME/tmp/arg0, so with a real codex on PATH they wrote into the developer's ~/.codex. Both now give codex a private HOME and CODEX_HOME and remove it afterwards. A free test runs each file against a stub codex that records HOME and CODEX_HOME and asserts neither is the caller's.
This commit is contained in:
1 parent
f6d2f3376d
commit
75a56be46f
3 files changed
+61
-3
No files matched your search
@@ -25,8 +25,19 @@ const CODEX_AVAILABLE = spawnSync('which', ['codex'], { timeout: 30_000 }).statu
|
||||
// The run pins the model with --ignore-user-config; older codex CLIs reject
|
||||
// the flag with an argv error indistinguishable from a Sol regression, so
|
||||
// probe support and skip (not fail) on old CLIs.
|
||||
const IGNORE_USER_CONFIG_SUPPORTED = CODEX_AVAILABLE
|
||||
&& (spawnSync('codex', ['exec', '--help'], { encoding: 'utf8', timeout: 120_000 }).stdout ?? '').includes('--ignore-user-config');
|
||||
// The probe runs in a private HOME and CODEX_HOME: every codex run writes
|
||||
// $CODEX_HOME/tmp/arg0, and module load must never touch the real ~/.codex.
|
||||
function codexSupportsIgnoreUserConfig(): boolean {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-sol-probe-'));
|
||||
try {
|
||||
fs.mkdirSync(path.join(home, '.codex'));
|
||||
const help = spawnSync('codex', ['exec', '--help'], {
|
||||
encoding: 'utf8', timeout: 120_000, env: { ...process.env, HOME: home, CODEX_HOME: path.join(home, '.codex') },
|
||||
});
|
||||
return (help.stdout ?? '').includes('--ignore-user-config');
|
||||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||||
}
|
||||
const IGNORE_USER_CONFIG_SUPPORTED = CODEX_AVAILABLE && codexSupportsIgnoreUserConfig();
|
||||
const evalsEnabled = !!process.env.EVALS;
|
||||
// External-service test — periodic tier only (CLAUDE.md tiering rule 3). The
|
||||
// positive guard shape below is what classifyPaidTestFile greps to exclude
|
||||
|
||||
@@ -956,3 +956,39 @@ describe('codex broken-install detection (#2742)', () => {
|
||||
expect(src).toContain('_CODEX_MP=$?');
|
||||
});
|
||||
});
|
||||
|
||||
// --- Live codex smokes keep away from the real Codex home -------------------
|
||||
|
||||
describe('live codex smokes run codex in a private HOME and CODEX_HOME', () => {
|
||||
// Every codex run writes $CODEX_HOME/tmp/arg0. The two smokes below probe the
|
||||
// CLI at module load or in free-tier tests, so with a real codex on PATH they
|
||||
// used to write into the developer's ~/.codex.
|
||||
for (const file of ['test/codex-resume-flag-semantics.test.ts', 'test/codex-e2e-sol-scope.test.ts']) {
|
||||
test(`${file} never runs codex with the caller's HOME or Codex home`, () => {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-codex-smoke-home-'));
|
||||
try {
|
||||
const stubDir = path.join(home, 'stub-bin');
|
||||
const log = path.join(home, 'codex-calls.log');
|
||||
fs.mkdirSync(stubDir);
|
||||
fs.writeFileSync(path.join(stubDir, 'codex'), `#!/usr/bin/env bash
|
||||
printf '%s|%s\\n' "$HOME" "\${CODEX_HOME:-unset}" >> "${log}"
|
||||
mkdir -p "\${CODEX_HOME:-$HOME/.codex}/tmp" && : > "\${CODEX_HOME:-$HOME/.codex}/tmp/arg0"
|
||||
printf 'Usage: codex exec [OPTIONS]\\n -c, --config <key=value> sandbox_mode\\n --ignore-user-config\\n'
|
||||
`, { mode: 0o755 });
|
||||
const r = spawnSync(process.execPath, ['test', file], {
|
||||
cwd: ROOT, encoding: 'utf8', timeout: 120_000,
|
||||
env: { PATH: `${stubDir}:${process.env.PATH ?? ''}`, HOME: home, TMPDIR: os.tmpdir() },
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
const calls = fs.readFileSync(log, 'utf8').trim().split('\n');
|
||||
expect(calls.length).toBeGreaterThan(0);
|
||||
for (const call of calls) {
|
||||
const [callHome, codexHome] = call.split('|');
|
||||
expect({ call, privateHome: callHome !== home, privateCodexHome: codexHome !== 'unset' && !codexHome!.startsWith(home) })
|
||||
.toEqual({ call, privateHome: true, privateCodexHome: true });
|
||||
}
|
||||
expect(fs.existsSync(path.join(home, '.codex'))).toBe(false);
|
||||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -12,11 +12,20 @@
|
||||
* (the periodic-tier eval runners) will exercise it.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { afterAll, describe, test, expect } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const codexPath = spawnSync('which', ['codex'], { encoding: 'utf-8', timeout: 30_000 }).stdout.trim();
|
||||
const codexAvailable = codexPath.length > 0;
|
||||
// Every codex run writes $CODEX_HOME/tmp/arg0, so the smoke gets a private
|
||||
// HOME and CODEX_HOME and never touches the developer's real ~/.codex.
|
||||
const smokeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-resume-smoke-'));
|
||||
fs.mkdirSync(path.join(smokeHome, '.codex'));
|
||||
const smokeEnv = { ...process.env, HOME: smokeHome, CODEX_HOME: path.join(smokeHome, '.codex') };
|
||||
afterAll(() => fs.rmSync(smokeHome, { recursive: true, force: true }));
|
||||
|
||||
describe.skipIf(!codexAvailable)(
|
||||
'codex exec resume — flag semantics (live CLI smoke; closes #1270 regex-only gap)',
|
||||
@@ -26,6 +35,7 @@ describe.skipIf(!codexAvailable)(
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: 10_000,
|
||||
env: smokeEnv,
|
||||
});
|
||||
const helpText = (result.stdout || '') + '\n' + (result.stderr || '');
|
||||
// The /codex skill builds resume invocations with `-c 'sandbox_mode="read-only"'`.
|
||||
@@ -39,6 +49,7 @@ describe.skipIf(!codexAvailable)(
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: 10_000,
|
||||
env: smokeEnv,
|
||||
});
|
||||
const helpText = (result.stdout || '') + '\n' + (result.stderr || '');
|
||||
// The whole point of #1270 was that `codex exec resume` rejects `-C <dir>`.
|
||||
|
||||
Reference in new issue
Block a user