Skip to content

[Bug]: Codex App update invalidates the persisted codex-runtime.json pin — the dead hashed path is never re-resolved #4035

Closed

Description

@h-dot-seo

Client or integration

Codex App

Area

CLI

Summary

~/.opencodex/codex-runtime.json persists an absolute path to the Codex App's bundled CLI, and that path lives inside a content-hashed directory:

%LOCALAPPDATA%\OpenAI\Codex\bin\<hash>\codex.exe

The Codex App replaces that hashed directory on every update and deletes the previous one. The persisted configured pin then points at a path that no longer exists, and runtime selection falls through to fallback:

Codex runtime: codex
Codex version: unknown
Codex source: fallback
⚠️  No validated Codex runtime found (path does not exist); falling back to `codex`.

A dead configured entry is never discarded or re-resolved. Because the Codex App does not put its hashed bin directory on PATH, the env → configured → shim → PATH → fallback chain introduced in #359 has nothing left to find, and codex does not resolve as a bare command on this host.

I expected a recorded runtime path that no longer exists to be treated as absent — dropped, with resolution continuing down the chain and the file re-persisted — rather than retained while runtime selection degrades to a bare codex fallback.

This is not #3442. That fix covers a pin whose path is real but whose recorded version is old (the entitlement client_version floor). This is a pin whose path does not exist at all.

Reproduction

Observed twice on the same host, the second time live within a single 15-minute window.

Instance 1 — how the pin died

  1. On 2026-09-02, codex-runtime.json recorded "command": "...\\OpenAI\\Codex\\bin\\b99306303521e97e\\codex.exe", "source": "configured", "selectedVersion": "0.151.0-alpha.7.2".
  2. On 2026-09-04, the Codex App updated to 0.153.0. The bundled CLI moved to ...\bin\9ba750cce02d5e5c\, and b99306303521e97e was removed.
  3. From then on, ocx status and ocx doctor reported source=fallback, version=unknown, and the "No validated Codex runtime found (path does not exist)" warning. This survived ocx service restart and an upgrade from 2.42.0 to 2.48.0.

Instance 2 — the same directory replacement observed live

  1. At 2026-09-08 ~21:26 KST, ...\OpenAI\Codex\bin\ contained 9ba750cce02d5e5c (holding codex.exe, codex-cli 0.153.0) and e5208f10301fe5e6 (holding rg.exe).
  2. A Codex App update was applied at ~21:38 KST.
  3. ...\bin\ then contained 8e5b6932251c2c1c and c60635126245daef. 9ba750cce02d5e5c no longer existed — roughly 15 minutes after being the live runtime directory.

Any pin written against a hashed directory is therefore invalidated by the next Codex App update.

A stable path exists and is not used

%USERPROFILE%\.codex\plugins\.plugin-appserver\codex.exe carries no hash and is updated in place. Across the update in step 5 it kept the same path and went from codex-cli 0.153.0 to codex-cli 0.153.4. Searching this repository for plugin-appserver returns no results, so the resolver does not appear to consider this location.

Workaround

Repointing command in codex-runtime.json to that stable path restored source=configured with an attested version, and it survived the step-5 update untouched. ocx doctor reports no warnings afterwards.

Analysis and suggested directions

  1. Validate configured at resolution time. When the recorded command does not exist, treat the entry as absent: continue down env → configured → shim → PATH → fallback and re-persist the result, instead of retaining a dead pin and degrading to fallback.
  2. Add the Codex App's stable location as a discovery candidate. %USERPROFILE%\.codex\plugins\.plugin-appserver\codex.exe (and the macOS/Linux equivalents) is the only Codex App path observed here that survives an update.
  3. Refresh selectedVersion on drift. After the workaround, the file still records 0.153.0 while the resolved binary reports 0.153.4. ocx status shows the probed value, so the recorded field goes stale. Harmless here — well above the 0.144.0 gated floor — but it is the same field that tier 2 of resolveCodexEntitlementClientVersion reads (fix(codex): raise a stale runtime's client version to the measured gated floor #3442).

Two notes on existing recovery paths:

  • ocx doctor --fix-codex-runtime, added in fix(codex): stable Codex runtime selection for sync/clamp (#297) #359, is not listed in ocx doctor --help on 2.48.0. I did not run it.
  • Even if it were available, it could not have recovered this host: with the pin dead and no Codex CLI on PATH, the chain has nothing to resolve to, which is exactly the observed fallback state.

Related: #359 (the resolver and codex-runtime.json), #3442 (stale-but-valid pin version), #356 (shim restore after external npm updates — the analogous problem for npm-installed CLIs).

Version

2.48.0

Operating system

Windows 11 Pro 10.0.26200

Provider and model

No response

Logs or error output

# Before — dead pin (2.48.0, after the 2026-09-04 Codex App update)
$ ocx status
   Codex runtime: codex
   Codex version: unknown
   Codex source: fallback
   /!\  No validated Codex runtime found (path does not exist); falling back to `codex`.
        Run ocx doctor for diagnosis and recovery.

$ ocx system codex-cli-update check --json
{
  "candidateAvailable": false,
  "versionEvidence": { "kind": "unavailable" },
  "provenance": "unknown",
  "managed": false,
  "reason": "candidate_unavailable",
  "location": null
}

# Intermediate — with the hashed dir temporarily added to PATH, selection
# succeeded, but the dead pin was still preferred over it
$ ocx doctor
  Codex runtime selection
    ok  Selected runtime: ...\OpenAI\Codex\bin\9ba750cce02d5e5c\codex.exe (0.153.0, source=path)
    !!  Preferred runtime unavailable; fell back to ...\bin\9ba750cce02d5e5c\codex.exe

# Hashed directory replacement, observed live
# 21:26 KST
9ba750cce02d5e5c/   e5208f10301fe5e6/
# 21:38 KST, after the Codex App update
8e5b6932251c2c1c/   c60635126245daef/
$ ls .../OpenAI/Codex/bin/9ba750cce02d5e5c
ls: cannot access '.../OpenAI/Codex/bin/9ba750cce02d5e5c': No such file or directory

# The stable path across that same update
# before: codex-cli 0.153.0    after: codex-cli 0.153.4
# path unchanged: %USERPROFILE%\.codex\plugins\.plugin-appserver\codex.exe

# After — pin repointed to the stable path
$ ocx status
   Codex runtime: C:\Users\[USER]\.codex\plugins\.plugin-appserver\codex.exe
   Codex version: 0.153.4
   Codex source: configured
$ ocx doctor
   (no warnings)

Screenshots and supporting files

No screenshots attached. Every value above was read from ocx status, ocx doctor, ocx system codex-cli-update check --json, directory listings under %LOCALAPPDATA%\OpenAI\Codex\bin\, and codex.exe --version. Account identifiers, tokens, credentials and quota data are excluded.

Redacted configuration

// ~/.opencodex/codex-runtime.json — the recorded command path no longer exists
{
  "version": 1,
  "command": "C:\\Users\\[USER]\\AppData\\Local\\OpenAI\\Codex\\bin\\b99306303521e97e\\codex.exe",
  "source": "configured",
  "selectedVersion": "0.151.0-alpha.7.2",
  "updatedAt": "2026-09-02T05:57:19.180Z"
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    cliCLI, config inject, packaging flags
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Sep 8, 2026
  2. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 68 / 80

    이 이슈는 Codex App을 업데이트하면 OpenCodex가 기억해 둔 Codex CLI 경로가 깨지는 버그를 말합니다. 지금 dev HEAD 29bb221c3(패키지 2.49.0)에는 #359에서 들어온 런타임 선택기가 src/codex/runtime.ts에 살아 있습니다. 우선순위는 env(CODEX_CLI_PATH) → configured(~/.opencodex/codex-runtime.json) → shim → PATH → fallback입니다. Codex App 쪽 바이너리는 보통 %LOCALAPPDATA%\OpenAI\Codex\bin\<내용해시>\codex.exe처럼 해시 폴더 안에 있고, 앱이 업데이트되면 그 해시 폴더 자체가 사라지고 새 해시 폴더가 생깁니다. 그런데 OpenCodex는 예전에 성공했던 절대 경로를 codex-runtime.json의 configured로 그대로 남겨 둡니다.

    재현 서술이 아주 구체적입니다. 9월 2일에 핀이 ...\bin\b99306303521e97e\codex.exe(0.151.0-alpha.7.2)였고, 9월 4일 Codex App이 0.153.0으로 올라가며 그 폴더가 지워졌습니다. 그 뒤 ocx status/ocx doctor는 source=fallback, version=unknown, “No validated Codex runtime found (path does not exist)” 경고를 냈고, 서비스 재시작·OpenCodex 2.42→2.48 업그레이드에도 살아남았습니다. 같은 날 21:26→21:38 KST에 해시 디렉터리가 9ba750cce02d5e5c에서 8e5b6932251c2c1c로 바뀌는 것까지 실시간으로 찍혀 있어서, “한 번 핀한 해시 경로는 다음 앱 업데이트에 무조건 죽는다”가 사실로 확인됩니다. 이건 #3442(경로는 살아 있는데 기록된 버전만 낡은 경우, entitlement client_version 바닥)과 다릅니다. 여기는 경로 파일이 아예 없는 경우입니다.

    현재 코드 동작을 맞춰 보면, probeVersion(src/codex/runtime.ts 약 286–293행)은 절대 경로가 없을 때 path does not exist로 거절합니다. resolveCodexRuntimeUncached(약 546–630행)는 거절된 configured 다음에 shim/PATH/fallback을 이어서 고를 수 있고, replacedConfigured 진단도 남깁니다. 문제는 그다음입니다. resolveAndPersistCodexRuntime(약 633–657행)는 result.runtime.source !== "fallback"일 때만 디스크에 다시 씁니다. 그래서 해시 핀이 죽고 PATH에도 Codex가 없으면 선택은 fallback으로 떨어지고, 죽은 configured 핀은 파일에서 지워지지 않습니다. 리포터가 본 “경고만 반복되고 핀은 영원히 남는” 증상과 정확히 맞습니다. 게다가 pathCandidates(약 363–375행)는 PATH만 보고, shimCandidates는 codex-shim.json만 봅니다. 리포터가 찾아 낸 안정 경로 %USERPROFILE%\.codex\plugins\.plugin-appserver\codex.exe(업데이트 중에도 경로 불변, 0.153.0→0.153.4)는 저장소에 plugin-appserver 문자열이 없고 후보 목록에도 없습니다. 임시로 해시 bin을 PATH에 넣으면 doctor가 path로 고르긴 하지만, 평소 Codex App은 그 bin을 PATH에 안 올리므로 일반 사용자는 자동 복구가 안 됩니다.

    ocx doctor --fix-codex-runtime(src/cli/doctor.ts 약 1004–1025행)도 이 케이스의 은탄환이 아닙니다. 새 후보(newerAvailable)가 있을 때만 핀을 갈아끼우고, 없으면 resolveAndPersistCodexRuntime만 호출합니다. fallback이면 위와 같이 죽은 핀을 지우지 않습니다. 리포터 말대로 2.48.0 ocx doctor --help에 이 플래그가 안 보여도, 플래그가 보여도 “핀 죽음 + PATH 공백”에서는 복구가 안 됩니다. #356(외부 npm 업데이트 뒤 shim 복구)과 같은 계열의 “외부 설치물이 자리를 옮기면 OpenCodex 기록이 따라가지 못함” 문제입니다. 제품 tip(#4002 등)과는 겹치지 않는 Windows Codex App 런타임 생존성 버그라서, types/config 분할 무효화 대상도 아닙니다.

    라인 293 - probeVersion이 없는 절대 경로를 거절하는 것은 맞지만, 그 결과가 디스크의 죽은 configured 핀을 지우거나 재탐색을 강제하지는 않는다
    라인 647 - resolveAndPersistCodexRuntime가 source === "fallback"이면 persist를 건너뛰어, 죽은 핀+공백 PATH 상황에서 codex-runtime.json이 영원히 남는다
    경로/심볼 pathCandidates - PATH만 스캔하고 Codex App의 해시 bin·plugin-appserver 안정 경로를 후보에 넣지 않는다
    경로/심볼 doctor --fix-codex-runtime - newerAvailable이 없으면 죽은 핀을 정리하지 못해, 리포터 재현 환경에서는 복구 수단이 되지 않는다
    경로/심볼 selectedVersion - 우회 후에도 파일의 버전이 실제 바이너리와 어긋날 수 있어(#3442 tier2가 읽는 같은 필드) 드리프트 갱신도 같이 볼 가치가 있다

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

    • 최소 수정만 할지(죽은 configured를 absent로 취급하고 체인 계속 + 성공 시 재persist, fallback이어도 죽은 핀 delete/클리어), 아니면 Codex App 안정 경로(plugin-appserver 및 macOS/Linux 대응)를 정식 discovery 후보로 넣을지
    • 해시 bin 디렉터리 자체를 스캔하는 것은 Codex App 내부 레이아웃에 의존하므로, 지원 범위로 넣을지 문서화만 할지
    • --fix-codex-runtime를 help에 노출하고, “핀 무효 시 강제 재탐색/클리어” 의미로 고칠지
    • fix(codex): raise a stale runtime's client version to the measured gated floor #3442(버전 바닥)와 이슈를 한 PR로 묶을지, 핀 생존성만 먼저 분리할지

    너의 추천
    bug/cli/service로 받아 두고, 1순위는 src/codex/runtime.ts에서 존재하지 않는 configured를 absent처럼 취급한 뒤 체인을 계속하며 성공 시 재persist(실패·fallback이어도 죽은 핀은 디스크에서 제거)하는 수정입니다. 가능하면 Windows·macOS·Linux의 Codex App 안정 경로를 discovery 후보에 추가하고, doctor 플래그 help/복구 의미를 맞추세요. 리포터 우회(핀을 plugin-appserver로 수동 변경)는 임시책으로 문서에만 남기면 됩니다. 중복 이슈로 닫을 대상은 아니고 #3442/#359/#356을 Related로 유지하세요.

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

  3. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Fixed on dev in ff524ae (via #4058).

    You were right that the dead pin is never re-resolved: resolveAndPersistCodexRuntime degrades to fallback when the hashed path is gone, but the persist branch declines a fallback result, so the stale configured entry survived and every later resolve re-probed a path that cannot exist. The pin is now retired when resolution degrades to fallback for that specific reason, and the failure names the persisted command so the log says what was dropped.

    The retirement is bounded three ways — only a fallback result, only the persisted command matched exactly, and only the path-does-not-exist rejection — so a present-but-unusable binary is still left for you to look at. Review hardened two edges before merge: the comparison is exact rather than case-insensitive, so a missing /plugins/codex cannot retire a live /plugins/Codex on Linux, and a failed removal now warns instead of silently leaving the pin authoritative.

    Closing manually because pull requests here target dev, and GitHub only auto-closes on merge into main.

  4. added a commit that references this issue on Sep 17, 2026
    a34ec9d
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 workingcliCLI, config inject, packaging flagsserviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions