Repository navigation
install deletes foreign tables placed between the managed MCP markers in ~/.codex/config.toml (0.10.4 → 0.11.0 upgrade) #2228
Description
Activity
- addededitor/integrationEditor compatibility and CLI integrationEditor compatibility and CLI integrationstability/performanceServer crashes, OOM, hangs, high CPU/memoryServer crashes, OOM, hangs, high CPU/memory
on Sep 16, 2026 - addedbugSomething isn't workingSomething isn't workingpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.Needs near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UXDisplay bugs, docs, adoption UX
on Sep 19, 2026 Thank you for the detailed before/after evidence and for separating the confirmed configuration loss from the additional observations. I checked current main (59a05eb):
cbm_toml_upsert_managed_blockreplaces the entire marked span, and its conflict check excludes that same span. That confirms the preservation gap you identified when foreign tables end up inside the markers.We are treating this as a high-priority installer data-preservation bug and keeping it open. Review needs to cover both preserving unrelated tables and handling extra keys in the owned table, without weakening the existing malformed-marker protections. The hook duplication and deadline-setting suggestions are separate concerns, not prerequisites for addressing the data loss. Please keep your restored configuration and backup; there is no need to repeat the destructive upgrade on your real settings.
Code checked:
codebase-memory-mcp/src/cli/config_toml_edit.c
Lines 1070 to 1134 in 59a05eb
int cbm_toml_upsert_managed_block(const char *file_path, const char *begin_marker, const char *end_marker, const char *block) { size_t block_len = 0U; if (!toml_valid_path(file_path) || !toml_valid_marker(begin_marker) || !toml_valid_marker(end_marker) || strcmp(begin_marker, end_marker) == 0 || !block || toml_bounded_length(block, TOML_EDIT_MAX_BYTES, &block_len) != TOML_EDIT_OK || !toml_text_is_safe(block, block_len, 1) || toml_contains_marker_line(block, block_len, begin_marker, end_marker) || toml_validate_lexical_strings(block, block_len) != TOML_EDIT_OK) { return TOML_EDIT_ERR; } char *existing = NULL; size_t existing_len = 0; toml_file_snapshot_t snapshot; if (toml_read_file(file_path, &existing, &existing_len, &snapshot) != TOML_EDIT_OK || !toml_text_is_safe(existing, existing_len, 1)) { free(existing); return TOML_EDIT_ERR; } toml_line_t begin_line = {0}, end_line = {0}; int has_pair = 0; if (toml_find_markers(existing, existing_len, begin_marker, end_marker, &begin_line, &end_line, &has_pair) != TOML_EDIT_OK) { free(existing); return TOML_EDIT_ERR; } size_t exclude_start = has_pair ? begin_line.start : SIZE_MAX; size_t exclude_end = has_pair ? end_line.full_end : SIZE_MAX; if (toml_managed_block_conflicts(existing, existing_len, exclude_start, exclude_end, block, block_len) != TOML_EDIT_OK) { free(existing); return TOML_EDIT_ERR; } toml_buffer_t output = {0}; size_t prefix_len = has_pair ? begin_line.start : existing_len; if (has_pair && prefix_len == 0U && existing_len >= 3U && (unsigned char)existing[0] == 0xefU && (unsigned char)existing[1] == 0xbbU && (unsigned char)existing[2] == 0xbfU) { prefix_len = 3U; } const char *newline = toml_newline_style(existing, existing_len); size_t payload_start = existing_len >= 3U && (unsigned char)existing[0] == 0xefU && (unsigned char)existing[1] == 0xbbU && (unsigned char)existing[2] == 0xbfU ? 3U : 0U; if (toml_buffer_append(&output, existing, prefix_len) != TOML_EDIT_OK || (!has_pair && existing_len > payload_start && existing[existing_len - 1] != '\n' && toml_buffer_append_cstr(&output, newline) != TOML_EDIT_OK) || toml_append_managed(&output, begin_marker, end_marker, block, newline) != TOML_EDIT_OK || (has_pair && toml_buffer_append(&output, existing + end_line.full_end, existing_len - end_line.full_end) != TOML_EDIT_OK)) { toml_buffer_dispose(&output); free(existing); return TOML_EDIT_ERR; } int result = toml_write_atomic(file_path, existing, existing_len, output.data, output.len, &snapshot); toml_buffer_dispose(&output); free(existing); return result; } - added a commit that references this issue
on Sep 30, 2026 Thank you very much, @daceconomy, for this report. The real before/after diff, the explanation of how Codex Desktop's appends ended up between our markers, and the pointer straight to
cbm_toml_upsert_managed_blockmade it quick to reproduce and fix. We're sorry the upgrade cost you your Codex Desktop settings, and we appreciate you restoring them by hand and still writing it all up so carefully.What changed: the installer no longer treats everything between
# >>> codebase-memory-mcp MCP >>>and# <<< … <<<as its own. It now reads that region table by table:[mcp_servers.codebase-memory-mcp]is ours. We rewrite only the keys we write (command,args,env_vars). Keys and comments you add there, like yourstartup_timeout_sec = 90, stay in the table, and so do sub-tables such as[mcp_servers.codebase-memory-mcp.env].- Every other table (
[mcp_servers.node_repl]and its.env,[desktop],[marketplaces.*]) is moved byte for byte, in its original order, to just below the closing marker. From then on the markers bracket only our entry, and Codex's later appends land outside them. - Uninstall removes only our table and the markers; everything else stays where it is.
- Where ownership can't be decided safely, the installer refuses and leaves the file untouched rather than guessing.
We chose to keep your tables rather than just refuse. Codex keeps appending below a closing marker that ends the file, so a refusing installer would have blocked every later install and uninstall until the TOML was hand-edited, and a hand edit is exactly where tables like these get lost.
The hook duplication and the hook-deadline observations are separate from this data-loss fix; we've noted them and will follow up on them separately. The fix is in #2467. Thanks again for the careful, reproducible report!
@DeusData, this is why #2434 keeps foreign tables in place: moving them past the closing marker introduces another way to lose that marker.
I reproduced this with #2434 (
0f308252), #2467 (98e58acb), temporary configs matching this issue's layout, and Codex app-server 0.149.1:- Call
cbm_toml_upsert_managed_block()from the respective PR head. - Delete the first foreign table through app-server
config/value/write:{"keyPath":"mcp_servers.node_repl","value":null,"mergeStrategy":"replace"} - Call the same CBM upsert again.
Observed helper results:
#2434: both markers remain; next upsert = 0 #2467: closing marker is gone; next upsert = -1 (file unchanged)In #2467, the closing marker is emitted before the moved sections. Codex deletes it along with
node_repl. The other tables andstartup_timeout_sec = 90survive; the failure is the subsequent update refusal. Deleting a later table, replacing the first table, and adding a table all passed on both heads.I'd keep foreign tables in place and determine ownership from table paths and managed keys, as #2434 does. If we move them, this edit-and-reinstall sequence needs regression coverage.
These checks used the unmodified CBM helpers under ASan/UBSan and the real app-server edit; I did not run the full installer or Desktop GUI.
- Call
I rechecked this on Linux x86_64 with full binaries built using
scripts/build.sh: #2434 at0f308252and #2467 at98e58acb, with Codex app-server 0.149.1. Each case used a fresh container and temporary config.Both initial installs and unchanged reinstalls succeeded. After deleting
mcp_servers.node_replthrough the sameconfig/value/writerequest, the fullinstall -y --dir=/root/bin --clients=codexreturned:- fix(config): recover Codex MCP after closing marker loss #2434: exit
0, both markers retained. - fix(install): keep foreign tables between the managed TOML markers (#2228) #2467: exit
1, closing marker missing. stderr:error: agent_config agent=Codex CLI op=mcp_install path=/root/.codex/config.toml (target: regular file, 961 bytes)
The failed step left
config.tomlbyte-identical; the installer kept the binary it had already published. Deleting the laterdesktoptable instead passed on both PRs.- fix(config): recover Codex MCP after closing marker loss #2434: exit
- added a commit that references this issue
on Oct 4, 2026 - added a commit that references this issue
on Oct 4, 2026 Thank you again, @daceconomy, for this careful report, and @astandrik for the follow-up testing.
#2467 landed on main in 8c12de9. The installer now reads the managed region table by table. It keeps your keys in
[mcp_servers.codebase-memory-mcp], moves every other table byte for byte to just below the closing marker, and refuses rather than guessing when ownership is unclear.Reopening, because @astandrik showed one case it does not cover yet. When Codex itself deletes a table through
config/value/write, the closing marker can disappear with it, and the next install then refuses (exit 1, file left untouched) instead of recovering. That follow-up is being worked out on #2434. This issue stays open until it lands.- added a commit that references this issue
on Oct 10, 2026
Version
install.sh:codebase-memory-mcp 0.11.0(upgrading from0.10.4)Platform
macOS 15 (Darwin 24.6.0), Intel (
darwin-amd64)Install channel
GitHub release archive via
install.sh(bash ~/.local/bin/install.sh, installs to~/.local/bin)Binary variant
standard
What happened, and what did you expect?
codebase-memory-mcp install(run byinstall.shduring the 0.10.4 → 0.11.0 upgrade) rewrote the managed# >>> codebase-memory-mcp MCP >>>…# <<< codebase-memory-mcp MCP <<<region of~/.codex/config.tomland deleted every table that other software had placed between those two markers:[mcp_servers.node_repl](+ its.envtable),[desktop], and all four[marketplaces.*]tables.~/.codex/config.tomlwent from 5,841 bytes to 4,371 bytes. Codex Desktop lost its marketplaces, desktop settings and its bundlednode_replMCP server. Nothing was printed for Codex; the install summary reportedmcp: ~/.codex/config.tomlas if it had succeeded.How the foreign tables got inside the managed region: the 0.10.4 install had appended its block at the end of the file, so the closing marker was the last line. Codex Desktop later appended its own tables, and the trailing comment line stayed last (apparently its TOML editor treats a trailing comment as attached to the document end — I did not verify that in Codex itself), which left the closing marker below Codex's tables. From that point on, the region between the markers contained one cbm-owned table and four foreign ones.
Expected: the installer should never remove a table it did not write.
cbm_toml_upsert_managed_blockshould fail closed (astoml_find_markersalready does for orphan markers, #1558) when the span between its markers contains any table header other than[mcp_servers.codebase-memory-mcp]— or, better, replace only the owned table and keep the rest of the span verbatim. The user-added key inside the owned table (startup_timeout_sec = 90, needed because the MCP server takes >10 s to start on a 1.5 GB index) was also dropped; preserving unknown keys, or at least printing a note, would be kinder.Reproduction
The real-world trigger was simply
bash ~/.local/bin/install.shon a machine whose~/.codex/config.tomlhad foreign tables between the cbm markers (state shown in the diff below).Sandboxed steps that should reproduce it without touching a real Codex configuration (
HOME,CODEX_HOME,TMPDIRand the cbm cache/runtime dirs all point at a temp directory). Caveat: I could not execute this on the affected machine — with a live cbm daemon running,installstops withactivation could not reserve exclusive access; no activation was committed, because the version-cohort locks live in/private/tmp/cbm-daemon-<uid>/and are account-wide rather than underCBM_CACHE_DIR/CBM_RUNTIME_DIR. It should run on a machine (or CI job) with no daemon. The evidence for the bug is the real diff and the code path, both below.Expected result (matches what happened for real):
[mcp_servers.node_repl],[desktop]and[marketplaces.openai-codex]are gone; only the regenerated[mcp_servers.codebase-memory-mcp]table remains between the markers.Logs
Diff of the real file, before → after the upgrade (values in
node_repl.envelided,$HOMEwritten as~). Lines 133–178 were replaced by the singleenv_varsline:Installer output for the Codex section (no warning about the removed tables):
Where it happens:
src/cli/config_toml_edit.c,cbm_toml_upsert_managed_block()copiesexisting[0 .. begin_line.start), appends the fresh managed block, then copiesexisting[end_line.full_end ..). Everything between the two marker lines is discarded, andtoml_managed_block_conflicts()only inspects the text outside that span, so foreign tables inside it are neither detected nor preserved.Project scale (if relevant)
Not index related. (For context: the machine's main index is 567 k nodes / 707 k edges, 1.5 GB.)
Related observations from the same upgrade (not the main report)
~/.codex/hooks.json: the twohook-augmententries had been edited locally to prefixCBM_HOOK_DEADLINE_MS=20000(see next bullet). The installer treated them as foreign, preserved them, and appended its own canonicalSessionStartandSubagentStartgroups, so Codex ended up with each hook twice. Matching on the binary path +hook-augmenttoken, rather than the exact command string, would avoid the duplicate.hook-augmenthas a built-in 2000 ms deadline (only overridable via theCBM_HOOK_DEADLINE_MSenv var). On this 1.5 GB index a hook query takes 4–6 s even with a warm daemon, so every hook silently exited empty (127deadline_exceededlines inlogs/hook-augment-timeouts.log) until the shims were hand-edited — which then makes them "not ours" for the installer. Aconfig set hook_deadline_mssetting honoured by the managed shims would avoid the whole conflict.Confirmations