Summary
The Codex installer's TOML block finder, findNextTableHeader in src/installer/targets/toml.ts, skips past [[array-of-tables]] headers instead of treating them as a boundary when locating the end of the [mcp_servers.codegraph] block. As a result, any [[...]] block located after the codegraph table in ~/.codex/config.toml is treated as part of the codegraph block and is silently deleted — on uninstall (removeTomlTable), and on install/upgrade when the block is rewritten (upsertTomlTable's replaced path). This contradicts the module's own doc comment ("Everything outside that block is preserved verbatim, byte-for-byte") and the intended invariant that sibling [[array_of_tables]] blocks survive. Verified against the real exported helpers that codex.ts calls.
Root cause
// src/installer/targets/toml.ts:140-154
function findNextTableHeader(content: string, from: number): number {
// Look for "\n[" but skip "\n[[" (array of tables).
let i = from;
while (i < content.length) {
const nlIdx = content.indexOf('\n[', i);
if (nlIdx === -1) return content.length;
if (content[nlIdx + 2] === '[') {
// [[...]] — keep searching past it.
i = nlIdx + 2; // <- skips the array-of-tables header instead of stopping at it
continue;
}
return nlIdx + 1;
}
return content.length;
}
Because \n[[ is skipped rather than treated as the end of the current block, blockEnd runs to EOF (or to the next single-bracket table), so everything from the codegraph header through the trailing [[...]] block is considered the codegraph block. removeTomlTable (toml.ts:116-120) and upsertTomlTable's replace branch (toml.ts:82-101) then drop that range.
Both are on the real Codex install/uninstall path: src/installer/targets/codex.ts:102 calls removeTomlTable on uninstall, and codex.ts:155 calls upsertTomlTable on install/upgrade.
Repro (executed against the real exported helpers)
import { removeTomlTable, upsertTomlTable, buildTomlTable }
from './dist/installer/targets/toml.js';
const file = `[mcp_servers.codegraph]
command = "codegraph"
args = ["serve", "--mcp"]
[[history]]
id = 1
note = "must survive"
`;
removeTomlTable(file, 'mcp_servers.codegraph');
// => { content: "", action: "removed" } // the entire [[history]] block is gone
const newBlock = buildTomlTable('mcp_servers.codegraph',
{ command: 'codegraph', args: ['serve','--mcp','--v2'] });
upsertTomlTable(file, 'mcp_servers.codegraph', newBlock);
// => { content: '[mcp_servers.codegraph]\ncommand = "codegraph"\nargs = ["serve", "--mcp", "--v2"]\n',
// action: "replaced" } // [[history]] dropped on the upgrade rewrite too
Expected: the [[history]] block is preserved (uninstall should leave [[history]] behind; upgrade should keep it below the rewritten codegraph table).
Suggested direction
findNextTableHeader should treat a \n[[ as a boundary too (stop there) — any new table header, single- or double-bracket, ends the current block. There is currently no test covering a sibling [[array-of-tables]] block in installer-targets.test.ts; a Codex fixture with a trailing [[...]] across install → upgrade-reinstall → uninstall would pin it.
Environment
main @ 2d72891, package 1.4.1, Node 22.23.1 (Linux). Reproduces on a clean npm ci && npm run build.
Summary
The Codex installer's TOML block finder,
findNextTableHeaderinsrc/installer/targets/toml.ts, skips past[[array-of-tables]]headers instead of treating them as a boundary when locating the end of the[mcp_servers.codegraph]block. As a result, any[[...]]block located after the codegraph table in~/.codex/config.tomlis treated as part of the codegraph block and is silently deleted — on uninstall (removeTomlTable), and on install/upgrade when the block is rewritten (upsertTomlTable'sreplacedpath). This contradicts the module's own doc comment ("Everything outside that block is preserved verbatim, byte-for-byte") and the intended invariant that sibling[[array_of_tables]]blocks survive. Verified against the real exported helpers thatcodex.tscalls.Root cause
Because
\n[[is skipped rather than treated as the end of the current block,blockEndruns to EOF (or to the next single-bracket table), so everything from the codegraph header through the trailing[[...]]block is considered the codegraph block.removeTomlTable(toml.ts:116-120) andupsertTomlTable's replace branch (toml.ts:82-101) then drop that range.Both are on the real Codex install/uninstall path:
src/installer/targets/codex.ts:102callsremoveTomlTableon uninstall, andcodex.ts:155callsupsertTomlTableon install/upgrade.Repro (executed against the real exported helpers)
Expected: the
[[history]]block is preserved (uninstall should leave[[history]]behind; upgrade should keep it below the rewritten codegraph table).Suggested direction
findNextTableHeadershould treat a\n[[as a boundary too (stop there) — any new table header, single- or double-bracket, ends the current block. There is currently no test covering a sibling[[array-of-tables]]block ininstaller-targets.test.ts; a Codex fixture with a trailing[[...]]across install → upgrade-reinstall → uninstall would pin it.Environment
main@2d72891, package1.4.1, Node 22.23.1 (Linux). Reproduces on a cleannpm ci && npm run build.