fix(server): Codex shadow homes keep their own cloud config bundle cache#26817

closed3
main
fix/issue-26811
opened6d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 5d ago

Problem

A Codex provider with a shadow home is disabled with Cannot create Codex shadow home entry 'cloud-config-bundle-cache.json' because '<shadow>/cloud-config-bundle-cache.json' already exists and is not a symlink. T3 links that cache from the main Codex home into every shadow home. Codex refreshes it with an atomic temp-and-rename write, which replaces the link with a regular file, and the next shadow-home setup rejects that file as a conflict. Reproduced on main with the real dev server: a shadow home that holds its own cache is disabled on start, and a linked one is disabled after a simulated Codex refresh and a restart.

Change

cloud-config-bundle-cache.json joins PRIVATE_ENTRY_NAMES, next to models_cache.json. Codex stores the cache per ChatGPT identity (chatgpt_user_id, account_id, signed payload) and ignores a cache from a different identity, so it was never meant to be shared. A shadow home now keeps its own cache file, a stale link to the shared copy is removed, and new shadow homes no longer get the link.

Scope and approval

Fixes pingdotgg/t3code#26811

Triaged via the automated issue flow; GitCafe issue #26811.

Verification

  • Red first: keeps a shadow home's own cloud config bundle cache and does not share the cloud config bundle cache with shadow homes in apps/server/src/provider/Drivers/CodexHomeLayout.test.ts fail on main (2 failed, 9 passed): the first with the reporter's CodexShadowHomeEntryConflictError, the second because a fresh shadow home gets the link. With the fix: 11/11 pass.
  • vp run check:changed passes.
  • Real dev server (T3CODE_DEV_SEED=0 vp run dev, Linux x64, codex-cli 0.163.0, synthetic homes): three Codex instances share one home. A: the shadow home holds its own cache. B: the cache is linked to the shared one. C: an empty shadow home.
StepBefore (main 7632c3dc70)After (fix)
First startA disabled with the conflict error; B and C read the main account's cache through a linkAll three start; A keeps its file, B's link is removed, C gets no link
Codex refresh (temp + rename) in the shadow home, then restartA and B disabledAll three start; each shadow home keeps its own cache; shared cache untouched

before-runtime.txt Β· after-runtime.txt Β· drivers: repro-setup.sh, codex-rewrite.sh, merge-settings.py

  • Not checked: a real ChatGPT-authenticated Codex refreshing the cache (the refresh is simulated with the same temp-and-rename write), and macOS, where it was reported. The report's config.toml conflict did not reproduce: with codex-cli 0.163.0, codex mcp add, codex features enable and app-server config/value/write and config/batchWrite all write through the shadow home's config.toml link and leave it a link (config-toml-check.txt, appserver-write.py). This PR leaves config.toml handling unchanged.
  • No UI changes, so there are no screenshots; this is a server-only fix proved by the tests and the runtime transcripts.

Models: Claude Opus 5.5 (lead, tests, implementation, review) through Claude Code in T3 Code; GPT-6.1-Sol (review) through the Codex harness.

πŸ€– Generated with Claude Code

Checks

22 successful Β· 18 skipped
All checks passed
a1796b3

Added 3 commits

  • Merge remote-tracking branch 'origin/main' into fix/issue-26811a1796b3
  • fix(server): Codex shadow homes keep their own cloud config bundle cacheda0beb6
  • test(server): reproduce Codex shadow homes sharing the cloud config bundle cachedfa0026
Synced from GitHub

Approvability

Verdict: Approved at a1796b3

Macroscope's review found this PR approvable β€” Codex shadow homes now keep their cloud-config bundle cache separate instead of linking to the shared home. Existing cache links are removed without deleting their targets, and direct-home behavior remains unchanged.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

βœ… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiBβœ…
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
CodexLive turn WebSocket wire1.3 KiB1.3 KiB0 B (0.0%)2.0 KiBβœ…
CodexLive turn WebSocket decoded21.6 KiB21.6 KiB0 B (0.0%)24.4 KiBβœ…
CodexLive turn messages220 (0.0%)8βœ…
ClaudeTotal thread wire5.0 KiB5.0 KiBβˆ’29 B (βˆ’0.6%)6.8 KiBβœ…
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBβœ…
ClaudeLive turn WebSocket wire1.3 KiB1.2 KiBβˆ’29 B (βˆ’2.3%)2.0 KiBβœ…
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiBβˆ’41 B (βˆ’0.2%)24.4 KiBβœ…
ClaudeLive turn messages21βˆ’1 (βˆ’50.0%)8βœ…

Baseline: dac8b11 Β· PR result: a1796b3 Β· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack β†’

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between dac8b11ddc25bd5da62a367fdb6c3de777ae2986 and a1796b31ccb3d3dca531a140a797398def80c80f.

External image Files selected for processing: 2
  • apps/server/src/provider/Drivers/CodexHomeLayout.test.ts
  • apps/server/src/provider/Drivers/CodexHomeLayout.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

Codex shadow-home materialization now treats cloud-config-bundle-cache.json as private. Tests cover existing shadow files, fresh entries, and symlinks.

Changes: Codex shadow cache

Layer / File(s)Summary
Private cache materialization
apps/server/src/provider/Drivers/CodexHomeLayout.ts, apps/server/src/provider/Drivers/CodexHomeLayout.test.ts
PRIVATE_ENTRY_NAMES now includes cloud-config-bundle-cache.json. Tests verify existing shadow cache contents remain unchanged, while fresh and symlinked shadow cache entries are removed without changing the shared cache.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix Β· Severity of issue fixed: Medium

Suggested reviewers: t3dotgg

Merge Risk: External image Minimal Β· up to a1796

The PR isolates Codex cache state without an established merge-blocking risk.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the primary fix: keeping the Codex shadow homes' cloud config bundle caches private.
Description checkPassedThe description includes all required sections. It explains the problem, the implementation, scope and approval reference, focused verification results, limitations, and agent attribution.
Linked Issues checkPassedThe change satisfies the coding requirement in issue #26811. CodexHomeLayout.ts adds cloud-config-bundle-cache.json to PRIVATE_ENTRY_NAMES, so new shadow homes do not receive the shared-cache li…
Out of Scope Changes checkPassedThe changed production code and tests are directly connected to issue #26811. The tests cover the new private-entry behavior. No unrelated production behavior or UI change is present in the reviewed d…
Full details: Linked Issues check

Explanation

The change satisfies the coding requirement in issue #26811. CodexHomeLayout.ts adds cloud-config-bundle-cache.json to PRIVATE_ENTRY_NAMES, so new shadow homes do not receive the shared-cache link and existing cache links are removed during materialization. The added tests in CodexHomeLayout.test.ts verify preservation of a shadow home's own cache, removal of stale links, and preservation of the shared cache.

Full details: Out of Scope Changes check

Explanation

The changed production code and tests are directly connected to issue #26811. The tests cover the new private-entry behavior. No unrelated production behavior or UI change is present in the reviewed diff.

Finishing Touches
πŸ“ Generate docstrings
  • Commit to this branch
  • Create a new PR
πŸ§ͺ Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix Β· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

cloud-requirements-cache.json has the same shape and should probably join PRIVATE_ENTRY_NAMES here too. Like the config bundle, it is a signed_payload + signature cache scoped to chatgpt_user_id, so sharing it across accounts is wrong for the same reason, and an atomic refresh in a shadow home would hit the same CodexShadowHomeEntryConflictError.

Seen on macOS: a secondary account's shadow home had cloud-config-bundle-cache.json replaced by a real file (the reported error), while cloud-requirements-cache.json was still a symlink to ~/.codex, holding the primary account's chatgpt_user_id.

Suggested addition:

ts
1const PRIVATE_ENTRY_NAMES = new Set([
2 "auth.json",
3 "auth.lock",
4 "models_cache.json",
5 "cloud-config-bundle-cache.json",
6 "cloud-requirements-cache.json",
7]);

The existing stale-link removal then cleans up already-linked shadow homes, as it does for the config bundle.