fix: preserve top-level config keys and use absolute agent paths for Codex ≥0.116

Two fixes for Codex config.toml compatibility:

1. ensureCodexHooksFeature: insert [features] before the first table header
   instead of prepending it before all content. Prepending traps bare
   top-level keys (model, model_reasoning_effort) under [features], where
   Codex rejects them with "invalid type: string, expected a boolean".

2. generateCodexConfigBlock: use absolute config_file paths when targetDir
   is provided. Codex ≥0.116 requires AbsolutePathBuf and cannot resolve
   relative "agents/..." paths, failing with "AbsolutePathBuf deserialized
   without a base path".

Fixes #1202

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
CI
2026-03-24 10:11:31 +08:00
parent 763c6cc642
commit 9f8d11d603
2 changed files with 91 additions and 8 deletions

View File

@@ -1128,7 +1128,12 @@ function generateCodexAgentToml(agentName, agentContent) {
* Generate the GSD config block for Codex config.toml.
* @param {Array<{name: string, description: string}>} agents
*/
function generateCodexConfigBlock(agents) {
function generateCodexConfigBlock(agents, targetDir) {
// Use absolute paths when targetDir is provided — Codex ≥0.116 requires
// AbsolutePathBuf for config_file and cannot resolve relative paths.
const agentsPrefix = targetDir
? path.join(targetDir, 'agents').replace(/\\/g, '/')
: 'agents';
const lines = [
GSD_CODEX_MARKER,
'',
@@ -1137,7 +1142,7 @@ function generateCodexConfigBlock(agents) {
for (const { name, description } of agents) {
lines.push(`[agents.${name}]`);
lines.push(`description = ${JSON.stringify(description)}`);
lines.push(`config_file = "agents/${name}.toml"`);
lines.push(`config_file = "${agentsPrefix}/${name}.toml"`);
lines.push('');
}
@@ -2124,7 +2129,21 @@ function ensureCodexHooksFeature(configContent) {
if (!configContent) {
return { content: featuresBlock, ownership: 'section' };
}
return { content: featuresBlock + eol + configContent, ownership: 'section' };
// Insert [features] before the first table header, preserving bare top-level keys.
// Prepending would trap them under [features] where Codex expects only booleans (#1202).
const firstTableHeader = lineRecords.find(r => r.tableHeader);
if (firstTableHeader) {
const before = configContent.slice(0, firstTableHeader.start);
const after = configContent.slice(firstTableHeader.start);
const needsGap = before.length > 0 && !before.endsWith(eol + eol);
return {
content: before + (needsGap ? eol : '') + featuresBlock + eol + after,
ownership: 'section',
};
}
// No table headers — append [features] after top-level keys
const needsGap = configContent.length > 0 && !configContent.endsWith(eol + eol);
return { content: configContent + (needsGap ? eol : '') + featuresBlock, ownership: 'section' };
}
function hasEnabledCodexHooksFeature(configContent) {
@@ -2249,7 +2268,7 @@ function installCodexConfig(targetDir, agentsSrc) {
fs.writeFileSync(path.join(agentsTomlDir, `${name}.toml`), tomlContent);
}
const gsdBlock = generateCodexConfigBlock(agents);
const gsdBlock = generateCodexConfigBlock(agents, targetDir);
mergeCodexConfig(configPath, gsdBlock);
return agents.length;