Skip to content

[Bug]: byte-identical catalog resync bumps the mtime and latches the app-server staleness check to stale #1459

Closed

Description

@tlsdnwn55

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

  1. macOS with the Codex App (ChatGPT desktop) running, so a long-lived /Applications/ChatGPT.app/Contents/Resources/codex … app-server process exists.
  2. ocx start with a settled config, so the sync reproduces the catalog it already wrote.
  3. Observe the catalog mtime move even though the file content is unchanged, and collectCodexAppServerCatalogState() report stale.
  4. Start a new Codex CLI session with a v2 spawn_agent surface and a configured injectionModel: 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 : fresh

Redacted 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

  1. added
    bugSomething isn't working
    catalogModel catalog, slugs, visibility, routed entries
    on Aug 11, 2026
  2. Ingwannu commented on Aug 11, 2026

    @Ingwannu
    Owner

    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 that collectCodexAppServerCatalogState() 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 reporting catalogWritten: 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.

  3. lidge-jun commented on Aug 11, 2026

    @lidge-jun
    Owner

    Fixed on dev (c7eec01ca + af80bb69a, via #1460).

    writeRetainedCatalogSync now skips a byte-identical write, so an ordinary ocx start or an unchanged dashboard resync no longer moves the catalog's mtime — and collectCodexAppServerCatalogState() therefore stops classifying every already-running Codex as holding an outdated in-memory catalog. A configured injectionModel / subagentModels roster 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") substitutes U+FFFD for every invalid byte, so a catalog holding a bare 0x80 decoded 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.ts 23 pass / 0 fail, tests/codex-convergence-account-selectors.test.ts 20 pass / 0 fail, with the new regression red against the string comparison and green after.

    Thanks for the precise report — the #1407 interaction is what made this worth fixing rather than filing as cosmetic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entries

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions