Skip to content

fix(codex): treat unchanged sync-cache as success - #5594

Closed
garysassano wants to merge 1 commit into
lidge-jun:devfrom
garysassano:fix/sync-cache-unchanged-exit
Closed

garysassano wants to merge 1 commit into
lidge-jun:devfrom
garysassano:fix/sync-cache-unchanged-exit

Conversation

@garysassano

@garysassano garysassano commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An explicit ocx sync-cache exited 1 when models_cache.json already contained the bytes derived from the current catalog. Cache invalidation returned the same false value for an unchanged cache and a failed rewrite, so the CLI mistook a successful no-op for a failure. This is reproducible in the composed CLI test on upstream dev before the fix.

Give the CLI a detailed cache-invalidation outcome while preserving the existing boolean result for other callers. Identical bytes now return success without rewriting the cache or restarting Codex; --json reports skippedReason: "unchanged". Malformed catalogs and failed writes still fail. Add a composed CLI regression, a focused outcome test, and documentation.

Verification

  • GIT_CONFIG_GLOBAL=/dev/null bun run test — passed on this branch after rebasing onto dev. The empty Git global config keeps this workstation's signing setting out of isolated test repositories.
  • GIT_CONFIG_GLOBAL=/dev/null bun test tests/codex-integration/codex-composed-acceptance.test.ts tests/codex-integration/codex-models-cache-invalidate.test.ts tests/codex-integration/codex-app-server-processes.test.ts — 79 passed, 1 platform skip.
  • bun run typecheck, bun run structure:check, bun run privacy:scan, and git diff upstream/dev...HEAD --check — passed.
  • cd docs-site && bun install --frozen-lockfile && bun run build — passed.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Review readiness checklist

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

Summary by CodeRabbit

  • New Features

    • ocx sync-cache now recognizes when the cache is already current and completes successfully without rewriting files or restarting Codex.
    • JSON output reports unchanged caches with ok: true, wrote: false, and a skipped reason of unchanged.
    • Cache synchronization now distinguishes successful writes, unchanged caches, missing catalogs, disabled states, and failures.
  • Bug Fixes

    • Invalid catalogs and failed cache writes continue to return errors and nonzero exit codes.
    • Unchanged cache files retain their existing modification time.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: bcf1c625-1023-4a77-95fc-9fb6ca05151e

📥 Commits

Reviewing files that changed from the base of the PR and between a4bdc03 and b94cbcc.

📒 Files selected for processing (8)
  • docs-site/src/content/docs/reference/cli/lifecycle.md
  • src/cli/dispatch.ts
  • src/codex/catalog/retained-sync.ts
  • src/codex/catalog/sync.ts
  • structure/catalog.md
  • tests/codex-integration/codex-app-server-processes.test.ts
  • tests/codex-integration/codex-composed-acceptance.test.ts
  • tests/codex-integration/codex-models-cache-invalidate.test.ts

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


📝 Walkthrough

Walkthrough

Changes

Cache synchronization outcomes

Layer / File(s) Summary
Structured cache invalidation outcomes
src/codex/catalog/retained-sync.ts, src/codex/catalog/sync.ts, tests/codex-integration/codex-models-cache-invalidate.test.ts
The invalidation API now returns named outcomes. The existing boolean wrapper returns true only for "written". Tests cover written, unchanged, and failed outcomes.
sync-cache result mapping
src/cli/dispatch.ts, tests/codex-integration/codex-app-server-processes.test.ts
sync-cache uses structured outcomes, reports unchanged caches as successful skips, and restarts Codex only after a write.
Documented and accepted behavior
docs-site/src/content/docs/reference/cli/lifecycle.md, structure/catalog.md, tests/codex-integration/codex-composed-acceptance.test.ts
Documentation and acceptance tests specify the unchanged-cache JSON result and preserve nonzero results for invalid catalogs or failed writes.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant sync-cache
  participant invalidateCodexModelsCacheWithPermitOutcome
  participant CodexAppServer
  sync-cache->>invalidateCodexModelsCacheWithPermitOutcome: synchronize model cache
  invalidateCodexModelsCacheWithPermitOutcome-->>sync-cache: return structured outcome
  alt outcome is written
    sync-cache->>CodexAppServer: restart after cache write
  else outcome is unchanged
    sync-cache-->>sync-cache: return successful no-op
  end
Loading

Merge Risk: ⚪ Minimal · up to b94cb

Cache synchronization preserves existing behavior while making unchanged caches successful no-ops without unnecessary rewrites or restarts.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 6 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: treating an unchanged sync-cache operation as successful.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 6 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 22, 2026
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed; the review readiness checklist is complete.

Review readiness checklist

  • ✅ All CI tests are green on my local testing.
  • ✅ I pushed my PR to the latest dev commit.
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

✅ 4/4 boxes ticked.

This pull request is already Ready for Review.
The review-ready label marks this PR as ready; review automation runs independently.
Maintainers: @lidge-jun @Ingwannu

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 26 / 80

이 PR은 ocx sync-cache가 이미 맞는 models_cache.json을 보고도 exit 1로 끝나던 버그를 고칩니다. 예전에는 캐시를 안 바꾼 경우와 쓰기 실패가 둘 다 false라서, CLI가 “성공한 아무 일도 없음”을 실패로 읽었습니다. 지금은 invalidateCodexModelsCacheWithPermitOutcome이 written / unchanged / missing_catalog / desired_disabled / failed를 나누고, 예전 boolean 함수는 written일 때만 true를 줍니다. CLI는 바이트가 같을 때 exit 0으로 두고 Codex를 재시작하지 않으며, --json에는 skippedReason: "unchanged"가 나갑니다. 잘못된 카탈로그·쓰기 실패는 그대로 실패입니다. base는 dev이고, 문서·구조 설명·composed/focused 테스트가 같이 들어 있습니다. types/config 분할·프리뷰 배포와는 무관합니다.

라인 - src/cli/dispatch.ts · human path desiredDisabled — injection이 OFF여도 allowWhenDesiredDisabled: true로 실제 무효화를 시도합니다. 그런데 결과가 unchanged(또는 missing_catalog/contended)이면, 먼저 “OFF라서 아무 쓰기도 안 했다”는 메시지를 찍고, 이어서 “이미 최신”/“카탈로그 없음” 메시지를 또 찍습니다. JSON 경로는 desiredDisabled 필드만 있어 괜찮고, 사람용 stdout만 앞뒤가 어긋납니다. composed 테스트는 --json만 검사해서 이 이중 메시지를 못 잡습니다.

라인 - src/cli/dispatch.ts · JSON 주석 — “benign skip이 두 가지”라고 적혀 있는데, 이제 unchanged / contended / no_catalog 세 가지입니다. 동작에는 영향 없고 주석만 낡았습니다.

라인 - 검증 — 작성자 로컬에서 관련 통합 테스트·typecheck·structure·docs build를 돌렸다고 적혀 있습니다. 이 글을 쓸 때 hygiene/label 등은 통과했고 CodeRabbit은 아직 진행 중이었습니다.

메인테이너의 판단이 필요한 지점

OFF인데도 명시적 sync-cache가 성공 no-op일 때, human path에서 OFF 안내를 아예 빼도 될지. 빼면 “이미 최신” 한 줄만 남아서 의도에 더 가깝습니다. OFF 안내를 남기려면 문구를 “OFF여도 명시 요청이라 시도했고, 결과는 …”처럼 고치는 편이 맞습니다.

너의 추천

원인 분리와 boolean 호환 유지는 맞고, 회귀 테스트도 unchanged vs failed를 잘 고정합니다. human path 이중 메시지만 한 줄 정리하면 머지해도 됩니다. 닫을 중복·무효 PR은 없고, types/config 분할 이야기는 이 PR과 무관합니다.

이 댓글은 grok-bot이 작성했습니다

lidge-jun added a commit that referenced this pull request Sep 23, 2026
ocx sync-cache exited 1 when models_cache.json was already current, because
an unchanged cache and a failed rewrite both surfaced as false. The cache
invalidation now reports written / unchanged / missing_catalog /
desired_disabled / failed; the CLI exits 0 for an unchanged cache, restarts
Codex only after a real write, and names the skip in --json.

On top of #5594: the human path no longer prints the integration-OFF
explanation before the real outcome (an explicit sync-cache refreshes
regardless of the toggle), the skip-count comment names all three benign
skips, and the composed acceptance test covers the human output and derives
the expected skip from whether an OFF sync left a catalog behind.

Carries #5594.

Co-authored-by: Gary Sassano <10464497+garysassano@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Sep 23, 2026
ocx sync-cache exited 1 when models_cache.json was already current, because
an unchanged cache and a failed rewrite both surfaced as false. The cache
invalidation now reports written / unchanged / missing_catalog /
desired_disabled / failed; the CLI exits 0 for an unchanged cache, restarts
Codex only after a real write, and names the skip in --json.

On top of #5594: the human path no longer prints the integration-OFF
explanation before the real outcome (an explicit sync-cache refreshes
regardless of the toggle), the skip-count comment names all three benign
skips, and the composed acceptance test covers the human output and derives
the expected skip from whether an OFF sync left a catalog behind.

Carries #5594.

Co-authored-by: Gary Sassano <10464497+garysassano@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Sep 23, 2026
…egration status, quota locks, discovery snapshots (#5680)

* fix(codex): keep a fresh local Codex home before config.toml exists (#5441)

On WSL an unset CODEX_HOME switched to a discovered Windows Desktop home
whenever ~/.codex/config.toml was missing, even when the local ~/.codex
directory already existed on a fresh install. Keep the local home when it is
a directory; only an absent path or a non-directory lets discovery pick the
Windows home, and an unexpected stat failure keeps the local home rather than
switching. Structure and the Codex integration guide (all locales) now
describe directory presence instead of config.toml presence.

Carries #5441.

Co-authored-by: Lee Sang Gyu <217872453+lee3Q@users.noreply.github.com>

* fix(codex): treat an unchanged sync-cache as success (#5594)

ocx sync-cache exited 1 when models_cache.json was already current, because
an unchanged cache and a failed rewrite both surfaced as false. The cache
invalidation now reports written / unchanged / missing_catalog /
desired_disabled / failed; the CLI exits 0 for an unchanged cache, restarts
Codex only after a real write, and names the skip in --json.

On top of #5594: the human path no longer prints the integration-OFF
explanation before the real outcome (an explicit sync-cache refreshes
regardless of the toggle), the skip-count comment names all three benign
skips, and the composed acceptance test covers the human output and derives
the expected skip from whether an OFF sync left a catalog behind.

Carries #5594.

Co-authored-by: Gary Sassano <10464497+garysassano@users.noreply.github.com>

* fix(codex): refresh persisted integration intent in status (#5588)

GET /api/native-integrations derived the Codex switch from the server's
startup config snapshot, so a completed Codex toggle did not show until the
proxy restarted. The status read now takes per-client intent from persisted
configuration.

On top of #5588: the same fresh intent is used for the Grok and Claude
Desktop rows, whose toggles also persist independently (every other field
still comes from the snapshot); a Codex OFF toggle whose native restore did
not complete keeps the row unsafe on later reads instead of deriving absent
from intent; tests cover the stale-snapshot read, an off-then-on round trip,
and a failed restore followed by a status read.

Carries #5588.

Co-authored-by: Gary Sassano <10464497+garysassano@users.noreply.github.com>

* fix(codex): retire stale short-window main-account hard locks (#5620)

The main-account hard lock kept an old 5h reading forever once an account
moved to weekly or monthly windows: policy merging retained omitted blocking
short usage, and that stale tuple outranked a fresh weekly reading. A single
fresh WHAM response now replaces the short tuple when its primary window is
explicitly at least 24h and the secondary and tertiary windows are explicit
null or also long. The replacement proof is per observation and never
persisted; the current window still blocks at 99%.

On top of #5620: a non-null long auxiliary window only counts as proof when
it carries a valid used_percent, since unknown usage must never release a
block; regression covers a monthly primary with a long secondary or tertiary
window that omits used_percent.

The policy trusts one reported topology rather than repeated observations;
that trade-off is documented in structure/providers/openai-tiers.md.

Carries #5620.

Co-authored-by: 정우철 <86232509+oocheol@users.noreply.github.com>

* fix(catalog): bind model discovery's token and destination to one snapshot (#5647)

The provider connection probe resolved a token and then rebuilt its URL from
the live credential store, and a refreshing catalog gather captured its URL
before resolving a refreshed token. A Copilot account switch, or a refresh
that moves an account's API host, could therefore pair one account's bearer
with another account's origin. Discovery now rebuilds the send from the same
snapshot that supplied the token, keeps separate flights per stored origin,
probes Devin at the snapshot's tenant address, and a key row never borrows a
stored OAuth account's origin.

On top of #5647: negative tests pin that a snapshot without an API host falls
back only to static configuration validated against the vendor allowlist or
the vendor default, never to the live store (Copilot account switch during
refresh; Devin row with a non-allowlisted configured base), and
structure/catalog.md states that rule.

Carries #5647.

Co-authored-by: Fred Amartey <43480311+FredAmartey@users.noreply.github.com>

* fix(codex): discover the WSL Desktop runtime under CODEX_HOME/bin/wsl (#5635)

Windows Codex Desktop in WSL app-server mode ships its Linux Codex binary
under the effective Codex home as bin/wsl/<version-hash>/codex. An Ubuntu
service whose PATH has no codex resolved no runtime, so the v2 transition
failed with "Executable not found in $PATH".

On Linux, runtime discovery now enumerates the direct hash-directory
children of <effective CODEX_HOME>/bin/wsl newest first, after an explicit
runtime, PATH and the ordinary install locations, and probes them through the
existing isolated --version seam. The list is re-read on every resolve, so a
Desktop update that replaces the hash directory is rediscovered instead of
trusted from a remembered path, and CODEX_HOME joins the process memo key.

Regressions: absent PATH, replaced hash directory, newest hash first,
explicit pin wins, PATH wins, unreadable bin/wsl, and no enumeration on
macOS.

Closes #5635.

* fix(catalog): restore a native row's multi-agent pin after a forced mode (#5636)

Returning from forced v1 to default left newer native rows (gpt-6-astra,
gpt-6-luna) pinned to v1 when the pristine catalog backup predated them:
default mode preserves a live pin that the baseline does not mention, and
after a forced pass nothing distinguished the forced stamp from a genuine
pin.

A forced v1/v2 pass now records the row's pre-override value once, as
opencodex_multi_agent_version_origin (a string pin or null), and repeated
forced passes never replace it. Default mode consumes the record: pristine
baseline and native pins still win, routed-row normalization is unchanged,
and only a native row the baseline predates is restored from the record.
Rows written before the record existed keep the non-destructive read.

Closes #5636.

* fix(codex): bootstrap a missing config.toml in an existing Codex home (#5422)

A fresh Codex install can have its home directory but no config.toml yet:
Codex writes it lazily, and an authless Desktop user who never signs in to
OpenAI may never get one. Injection treated that as "Codex config not found
... Is Codex installed?" and blocked third-party provider onboarding.

When the resolved Codex home is a directory and config.toml is missing, an
applying injection now creates an empty config.toml exclusively (an existing
file is never overwritten) and continues; a validate-only preflight reasons
about that empty file and writes nothing. A missing home directory is still
refused, now with instructions to start Codex once or set CODEX_HOME, so a
wrong home stays distinguishable from an uninitialized one.

The client-connect preflight rollback scenario used a missing config.toml
as its fault; it now uses a deterministic injection refusal (ambiguous
managed sub-agent markers) instead.

Closes #5422.

* fix(clients): accept a relocated Aside root behind a symlinked ~/.aside (#5648)

A user who moved ~/.aside (for example to an external volume) and left a
symlink behind could not load Aside profiles: the reader refused the root
because the path itself was a link, although Aside follows it.

asideHomeDir now canonicalizes only that top-level alias, once, and only
onto a directory. Every boundary below the canonical root is unchanged: u/,
account directories and models.json still refuse links, and a ~/.aside link
to a regular file is still refused. Regressions cover the relocated root,
linked u/ and account directories and a linked catalog under it.

Closes #5648.

---------

Co-authored-by: Lee Sang Gyu <217872453+lee3Q@users.noreply.github.com>
Co-authored-by: Gary Sassano <10464497+garysassano@users.noreply.github.com>
Co-authored-by: 정우철 <86232509+oocheol@users.noreply.github.com>
Co-authored-by: Fred Amartey <43480311+FredAmartey@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Carried onto dev in bundle PR #5680 (squash-merged as aa2406b), rebuilt on current dev as commit 88c3416 on the lane branch with a Co-authored-by trailer for you, so the credit stays on the merged commit. Closing this one as superseded. Thank you for the fix.

@lidge-jun lidge-jun closed this Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working review-ready

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants