* fix(install): replace all ~/.claude/ paths in generated Codex .toml files (#2320) installCodexConfig() only rewrote get-shit-done/-scoped paths; all other ~/.claude/ references (hooks, skills, configDir) leaked into generated .toml files unchanged. Add three additional regex replacements to catch $HOME/.claude/, ~/.claude/, and ./.claude/ patterns and rewrite them to .codex equivalents. Adds regression test PATHS-01. Closes #2320 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(install): handle bare .claude end-of-string and scan all .toml files (CR feedback) - Use capture group (\/|$) so replacements handle both ~/.claude/ and bare ~/.claude at end of string, not just the trailing-slash form - Expand PATHS-01 test to scan agents/*.toml + top-level config.toml - Broaden leak pattern to match ./.claude, ~, and $HOME variants with or without trailing slash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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') || '';
|
||||
|
||||
@@ -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) ─────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user