Skip to content

Codex installer: findNextTableHeader skips past [[array-of-tables]], silently deleting sibling [[...]] blocks in ~/.codex/config.toml on install/upgrade/uninstall — verified #1351

Description

@inth3shadows

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions