Repository navigation
[Bug]: byte-identical catalog resync bumps the mtime and latches the app-server staleness check to stale #1459
Description
Client or integration
Codex CLI
Area
Catalog / models
Summary
writeRetainedCatalogSync writes the Codex catalog unconditionally — replaceActiveCodexCatalog → atomicWriteFile, with no comparison against what is already on disk. So every sync moves the catalog file's mtime even when the produced bytes are identical and the advertised model set did not change.
collectCodexAppServerCatalogState() compares that mtime against each running Codex's start time, so an ordinary ocx start — or any dashboard/CLI action that re-syncs an unchanged model set — classifies every already-running Codex as holding an outdated in-memory catalog. Since #1407 that verdict withholds all opencodex-authored v2 model guidance, so a configured injectionModel / subagentModels roster silently stops reaching the session for the rest of that Codex's lifetime, even though nothing about the catalog changed.
A long-lived Codex App app-server outlives every proxy restart, which makes the state effectively latched: restarting the Codex CLI does not clear it, and the only way out is quitting the Codex App or ocx sync --restart-codex.
Expected: a sync that produces no change should not make a running Codex look stale. #1046 covered warning about stale app-servers after a catalog rewrite; it did not cover avoiding the rewrite when there is nothing to write.
Reproduction
- macOS with the Codex App (ChatGPT desktop) running, so a long-lived
/Applications/ChatGPT.app/Contents/Resources/codex … app-serverprocess exists. ocx startwith a settled config, so the sync reproduces the catalog it already wrote.- Observe the catalog mtime move even though the file content is unchanged, and
collectCodexAppServerCatalogState()reportstale. - Start a new Codex CLI session with a v2
spawn_agentsurface and a configuredinjectionModel: no preferred model and no roster reach the session.
Measured on the affected machine (collectCodexAppServerCatalogState()):
state: stale
catalogMtime: 2026-08-11T02:52:27.788Z
pid 80794 start 2026-08-11T02:43:11.000Z stale: true <- Codex App app-server
pid 95407 start 2026-08-11T02:55:35.000Z stale: false <- codex-code-mode-host (live CLI session)
pid 91809 start 2026-08-11T02:53:12.000Z stale: false <- codex-code-mode-host (live CLI session)
The mtime bump on identical bytes, reproduced hermetically through the same writer:
identical bytes: true
mtime before/after identical rewrite: 2026-08-11T04:08:17.643Z -> 2026-08-11T04:08:18.746Z bumped: true
Version
2.12.0
Operating system
macOS 15.3.1
Provider and model
openai (ChatGPT login) / gpt-5.6-luna
Logs or error output
# state on the affected machine, and the same state with the Codex App process excluded
verdict incl. the Codex App app-server : stale
verdict excl. the Codex App app-server : freshRedacted configuration
{
"multiAgentGuidanceEnabled": true,
"injectionModel": "gpt-5.6-luna",
"injectionEffort": "max",
"subagentModels": ["gpt-5.6-luna"]
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Aug 11, 2026
Confirmed against current
dev: this is a real catalog-state bug, not only a warning/UI symptom. The retained sync still reaches the catalog writer for an unchanged materialized catalog, and an unconditional atomic replacement advances the mtime thatcollectCodexAppServerCatalogState()uses as its stale boundary. A long-lived app-server can therefore be classified stale even though the advertised bytes did not change.PR #1460 is the correct focused implementation direction, so I am not opening a duplicate fix. I reviewed its exact head and reproduced its focused tests (100 pass / 0 fail), typecheck, and privacy scan. One blocker remains before merge: its helper currently decodes both sides as UTF-8 strings rather than comparing exact bytes, so malformed UTF-8 can collapse to
U+FFFD, be mistaken for equality, and leave a corrupted catalog unrepaired while reportingcatalogWritten: false. I requested a byte-exact comparison plus a regression on the PR.Keeping this issue open until #1460 addresses that boundary, completes its review-readiness checklist, and receives exact-head CI/maintainer approval.
docs(devlog): plan the seven paired bug-fix landings
fix(catalog): compare catalog bytes, not decoded strings, in the no-o…
lidge-jun commentedon Aug 11, 2026
Fixed on
dev(c7eec01ca+af80bb69a, via #1460).writeRetainedCatalogSyncnow skips a byte-identical write, so an ordinaryocx startor an unchanged dashboard resync no longer moves the catalog's mtime — andcollectCodexAppServerCatalogState()therefore stops classifying every already-running Codex as holding an outdated in-memory catalog. A configuredinjectionModel/subagentModelsroster keeps reaching the session.One correction on top of the original fix: the comparison had to be byte-exact rather than string-exact.
readFileSync(path, "utf8")substitutesU+FFFDfor every invalid byte, so a catalog holding a bare0x80decoded equal to prepared content holding a real replacement character — the no-op guard would have preserved that corruption instead of repairing it.Verified:
tests/codex-catalog-sync-hardening.test.ts23 pass / 0 fail,tests/codex-convergence-account-selectors.test.ts20 pass / 0 fail, with the new regression red against the string comparison and green after.Thanks for the precise report — the
#1407interaction is what made this worth fixing rather than filing as cosmetic.docs(devlog): plan the seven paired bug-fix landings
fix(catalog): compare catalog bytes, not decoded strings, in the no-o…
fix(catalog): skip byte-identical models-cache writes so a start cann…
fix(catalog): skip byte-identical models-cache writes so a start cann…
fix(catalog): skip byte-identical models-cache writes so a start cann…
fix(catalog): skip byte-identical models-cache writes so a start cann…
fix(catalog): skip byte-identical models-cache writes so a start cann…