Merge pull request #1142 from medhatgalal/AI/fix-codex-markerless-gsd-merge

Fix duplicate Codex GSD agent blocks without marker
This commit is contained in:
Tom Boucher
2026-03-19 12:11:48 -04:00
committed by GitHub
2 changed files with 44 additions and 3 deletions

View File

@@ -1002,6 +1002,10 @@ function generateCodexConfigBlock(agents) {
return lines.join('\n');
}
function stripCodexGsdAgentSections(content) {
return content.replace(/^\[agents\.gsd-[^\]]+\]\n(?:(?!\[)[^\n]*\n?)*/gm, '');
}
/**
* Strip GSD sections from Codex config.toml content.
* Returns cleaned content, or null if file would be empty.
@@ -1027,7 +1031,7 @@ function stripGsdFromCodexConfig(content) {
cleaned = cleaned.replace(/^default_mode_request_user_input\s*=\s*true\s*\n?/m, '');
// Remove [agents.gsd-*] sections (from header to next section or EOF)
cleaned = cleaned.replace(/^\[agents\.gsd-[^\]]+\]\n(?:(?!\[)[^\n]*\n?)*/gm, '');
cleaned = stripCodexGsdAgentSections(cleaned);
// Remove [features] section if now empty (only header, no keys before next section)
cleaned = cleaned.replace(/^\[features\]\s*\n(?=\[|$)/m, '');
@@ -1061,7 +1065,7 @@ function mergeCodexConfig(configPath, gsdBlock) {
let before = existing.substring(0, markerIndex).trimEnd();
if (before) {
// Strip any GSD-managed sections that leaked above the marker from previous installs
before = before.replace(/^\[agents\.gsd-[^\]]+\]\n(?:(?!\[)[^\n]*\n?)*/gm, '');
before = stripCodexGsdAgentSections(before);
before = before.replace(/^\[agents\]\n(?:(?!\[)[^\n]*\n?)*/m, '');
before = before.replace(/\n{3,}/g, '\n\n').trimEnd();
@@ -1074,7 +1078,14 @@ function mergeCodexConfig(configPath, gsdBlock) {
// Case 3: No marker — append GSD block
let content = existing;
content = content.trimEnd() + '\n\n' + gsdBlock + '\n';
content = stripCodexGsdAgentSections(content);
content = content.replace(/\n{3,}/g, '\n\n').trimEnd();
if (content) {
content = content + '\n\n' + gsdBlock + '\n';
} else {
content = gsdBlock + '\n';
}
fs.writeFileSync(configPath, content);
}

View File

@@ -354,6 +354,36 @@ describe('mergeCodexConfig', () => {
assert.ok(content.includes('[agents.gsd-executor]'), 'has agent');
});
test('case 3 strips existing [agents.gsd-*] sections before appending fresh block', () => {
const configPath = path.join(tmpDir, 'config.toml');
const existing = [
'[model]',
'name = "o3"',
'',
'[agents.custom-agent]',
'description = "user agent"',
'',
'',
'[agents.gsd-executor]',
'description = "old"',
'config_file = "agents/gsd-executor.toml"',
'',
].join('\n');
fs.writeFileSync(configPath, existing);
mergeCodexConfig(configPath, sampleBlock);
const content = fs.readFileSync(configPath, 'utf8');
const gsdAgentCount = (content.match(/^\[agents\.gsd-executor\]\s*$/gm) || []).length;
const markerCount = (content.match(new RegExp(GSD_CODEX_MARKER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
assert.ok(content.includes('[model]'), 'preserves user content');
assert.ok(content.includes('[agents.custom-agent]'), 'preserves non-GSD agent section');
assert.strictEqual(gsdAgentCount, 1, 'keeps exactly one GSD agent section');
assert.strictEqual(markerCount, 1, 'adds exactly one marker block');
assert.ok(!/\n{3,}# GSD Agent Configuration/.test(content), 'does not leave extra blank lines before marker block');
});
test('idempotent: re-merge produces same result', () => {
const configPath = path.join(tmpDir, 'config.toml');
mergeCodexConfig(configPath, sampleBlock);