diff --git a/bin/install.js b/bin/install.js index 8d5543107..969e1f9dd 100755 --- a/bin/install.js +++ b/bin/install.js @@ -3016,6 +3016,12 @@ function installCodexConfig(targetDir, agentsSrc) { // Replace full .claude/get-shit-done prefix so path resolves to codex GSD install content = content.replace(/~\/\.claude\/get-shit-done\//g, codexGsdPath); content = content.replace(/\$HOME\/\.claude\/get-shit-done\//g, codexGsdPath); + // Replace remaining .claude paths with .codex equivalents (#2320). + // Capture group handles both trailing-slash form (~/.claude/) and + // bare end-of-string form (~/.claude) in a single pass. + content = content.replace(/\$HOME\/\.claude(\/|$)/g, '$HOME/.codex$1'); + content = content.replace(/~\/\.claude(\/|$)/g, '~/.codex$1'); + content = content.replace(/\.\/\.claude(\/|$)/g, './.codex$1'); const { frontmatter } = extractFrontmatterAndBody(content); const name = extractFrontmatterField(frontmatter, 'name') || file.replace('.md', ''); const description = extractFrontmatterField(frontmatter, 'description') || ''; diff --git a/tests/codex-config.test.cjs b/tests/codex-config.test.cjs index 60f863dea..671b5266e 100644 --- a/tests/codex-config.test.cjs +++ b/tests/codex-config.test.cjs @@ -810,6 +810,34 @@ describe('installCodexConfig (integration)', () => { assert.ok(checkerToml.includes('name = "gsd-plan-checker"'), 'plan-checker has name'); assert.ok(checkerToml.includes('sandbox_mode = "read-only"'), 'plan-checker is read-only'); }); + + // PATHS-01: no ~/.claude references should leak into generated .toml files (#2320) + // Covers both trailing-slash and bare end-of-string forms, and scans all .toml + // files (agents/ subdirectory + top-level config.toml if present). + (hasAgents ? test : test.skip)('generated .toml files contain no leaked ~/.claude paths (PATHS-01)', () => { + const { installCodexConfig } = require('../bin/install.js'); + installCodexConfig(tmpTarget, agentsSrc); + + // Collect all .toml files: per-agent files in agents/ plus top-level config.toml + const agentsDir = path.join(tmpTarget, 'agents'); + const tomlFiles = fs.readdirSync(agentsDir) + .filter(f => f.endsWith('.toml')) + .map(f => path.join(agentsDir, f)); + const topLevel = path.join(tmpTarget, 'config.toml'); + if (fs.existsSync(topLevel)) tomlFiles.push(topLevel); + assert.ok(tomlFiles.length > 0, 'at least one .toml file generated'); + + // Match ~/.claude, $HOME/.claude, or ./.claude with or without trailing slash + const leakPattern = /(?:~|\$HOME|\.)\/\.claude(?:\/|$)/; + const leaks = []; + for (const filePath of tomlFiles) { + const content = fs.readFileSync(filePath, 'utf8'); + if (leakPattern.test(content)) { + leaks.push(path.relative(tmpTarget, filePath)); + } + } + assert.deepStrictEqual(leaks, [], `No .toml files should contain .claude paths; found leaks in: ${leaks.join(', ')}`); + }); }); // ─── Codex config.toml [features] safety (#1202) ─────────────────────────────