Client or integration
Claude Code
Area
Proxy and routing
Summary
On Windows, ocx claude prints both of these on almost every launch:
⚠ 모델 컨텍스트 정보를 불러오지 못했습니다 — 1M 자동 표시는 이번 실행에서 생략됩니다.
⚠ Gateway model cache could not be refreshed; the model picker may be stale.
Neither is an auth or connectivity failure. Both are timeouts. cmdClaude gives
fetchClaudeContextWindows (/api/claude-code) and refreshGatewayModelCacheFromProxy
(/v1/models) a 3s budget each, and the local proxy takes 5-8s to answer. Both catch
blocks swallow the AbortError and emit the generic warning above.
The user-visible consequences are quiet but real: [1m] context marking is skipped for
the run, and ~/.claude/cache/gateway-models.json is never rewritten. Because Claude Code
only refreshes that cache itself when it holds a credential — and the subscription-preserving
launch deliberately injects none — the picker keeps serving whatever is on disk. Mine was
6 days stale (80 entries) before I traced this.
Expected: both endpoints answer within the 3s budget, so the cache is rewritten and [1m]
marking applies. Actual: both abort, every launch, and the warnings suggest an auth problem
rather than a latency one.
Root cause is a codex --version subprocess probe sitting on the request path, which is
effectively uncached for two independent reasons (details below).
Reproduction
-
Install @bitkyc08/opencodex 2.7.42 on Windows with a codex CLI present, plus several
additional codex launchers reachable via PATH (mine: npm global shim, a Codex
App-managed binary, and a shim backup — 102 probe candidates in total).
-
Start the proxy: ocx start --port 10100.
-
Run ocx claude --version.
-
Observe both warnings on stderr.
-
Time the two endpoints cmdClaude calls directly:
Measure-Command { Invoke-WebRequest 'http://127.0.0.1:10100/api/claude-code' -UseBasicParsing }
Measure-Command { Invoke-WebRequest 'http://127.0.0.1:10100/v1/models?limit=1000&ids=cli' -Headers @{'anthropic-version'='2023-06-01'} -UseBasicParsing }
Both exceed 3000 ms on a cold proxy. Note that repeating step 5 after a >60s idle gap
reproduces it again, because BUNDLED_CATALOG_CACHE_MS is 60s.
-
Confirm the effect: ~/.claude/cache/gateway-models.json keeps its old fetchedAt.
Timed in-process against the same config, to isolate where the time goes:
fetchAllModels 457 ms (all 7 providers, network included)
listCatalogNativeSlugs 1328 ms <-- no network at all
visibleNativeSlugs 1645 ms
nativeOpenAiSlugs 2072 ms
buildClaudeContextWindows 2018 ms
All of the native-slug cost is readCurrentCatalogOrCache() → loadBundledCodexCatalog().
The subprocess it wraps is not the problem:
codex debug models --bundled 87 ms
codex --version 721 ms
loadBundledCodexCatalog() three times in a row, nothing changing on disk, 60s cache:
#0 1214 ms
#1 1136 ms <-- expected ~0 on a warm cache
#2 1186 ms
Cause 1 — the bundled-catalog cache can never be hit
In src/codex/catalog/bundled.ts, candidates is computed in an IIFE that runs before
the bundledCatalogCache lookup, because cacheKey is assigned inside it. So
resolveAndPersistCodexRuntime() runs on every call, warm hits included. That would still
be cheap if the resolve were memoized, but line 154 forwards the real execFileSync:
const execFile = deps.execFileSync ?? (execFileSync as unknown as ExecFile);
// ...
const resolved = resolveAndPersistCodexRuntime({
execFileSync: execFile, // always truthy
and resolveCacheKey() in src/codex/runtime.ts deliberately refuses to memoize
injected-dependency resolves:
if (deps.execFileSync || deps.existsSync || deps.readFileSync || deps.configDir || deps.now) {
return null;
}
The guard reads correct in intent (injected deps imply a test double), but this caller
defeats it for the real-runtime path by passing its own already-defaulted value.
There is a related self-invalidation: resolveAndPersistCodexRuntime() (runtime.ts:505)
calls persistCodexRuntime unconditionally for any non-fallback selection, and
persistCodexRuntime() ends with resolveCache = null (runtime.ts:228). Since
persistedRuntimeCacheStamp() also folds the on-disk updatedAt into the cache key, each
write both clears the memo and changes the key it would have been stored under — even when
the resolved runtime is byte-identical to what is already persisted.
Cause 2 — cold reads probe every codex on PATH
bundled.ts:161 passes discoverAlternatives: deps.discoverAlternatives, i.e. undefined
for normal callers, which resolveCodexRuntimeUncached() treats as "discover". It then
probes every candidate to populate newerAvailable, each probe spawning codex --version
with a fresh mkdtempSync sandbox:
resolveCodexRuntime({}) 1224 ms failures: 102
resolveCodexRuntime({ discoverAlternatives: false }) 60 ms
Both select the identical runtime. Catalog loading only consumes resolved.runtime.command;
it never reads newerAvailable or failures. So on a cold read that ~1.2s scan is pure
overhead, and on its own eats more than a third of the 3s budget before any real work.
Effect
/api/claude-code measured over HTTP:
|
before |
after local patch |
| cold (fresh proxy) |
5000-8300 ms |
165 ms |
| warm |
6300 ms |
~25 ms |
| after 65s idle (cache expired) |
6325 ms |
1440 ms |
With the patches ocx claude runs clean, and gateway-models.json refreshed from 80 stale
entries to 98 current ones, 25 carrying [1m].
Patches I verified locally
Not opening a PR since you may prefer different tradeoffs, but these are what I tested.
src/codex/catalog/bundled.ts — forward an injected execFileSync only, so real-runtime
resolves stay memoizable:
...(deps.execFileSync ? { execFileSync: deps.execFileSync } : {}),
src/codex/catalog/bundled.ts — default the catalog path to non-discovering:
discoverAlternatives: deps.discoverAlternatives ?? false,
src/codex/runtime.ts — only persist when the selection actually changed, so the write
stops clearing its own memo:
const persistedRuntime = loadPersistedCodexRuntime(deps);
const selectionUnchanged = persistedRuntime !== null
&& persistedRuntime.command === result.runtime.command
&& persistedRuntime.source === result.runtime.source
&& (persistedRuntime.selectedVersion ?? null) === result.runtime.version;
if (result.runtime.command && result.runtime.source !== "fallback" && !selectionUnchanged) {
Tradeoff worth flagging on the second one: catalog-path callers lose newerAvailable
diagnostics. If something depends on those, moving discovery to a slow background interval
instead of every catalog read would preserve them while keeping the request path fast.
Suggested improvements beyond the fix
- Check
bundledCatalogCache before resolving the runtime. Deriving the cache key from
the very thing the cache exists to avoid means a warm hit can only ever save the
codex debug models call (87 ms), never the probe (1.2 s) that dominates. A cheaper key
would let warm hits skip both.
- Attribute the timeout in the warning text. Both messages read like auth or network
faults. Something like timed out after 3000ms would point straight at the cause; I went
looking at OAuth state and provider connectivity first.
- Consider raising or making the 3s budget configurable. Even fully patched, a cold read
is ~1.4s here, so a slower machine or a longer PATH could still trip it silently.
- A regression test that a repeated
loadBundledCodexCatalog() spawns no subprocess
would have caught both causes.
Version
2.7.42
Operating system
Windows 11
Provider and model
Not provider-specific. Reproduced with openai (forward), kiro, kiro-native,
opencode-go, xai, and google-antigravity configured; the slow path is local catalog
loading, not any upstream call.
Logs or error output
$ ocx claude --version
⚠ 모델 컨텍스트 정보를 불러오지 못했습니다 — 1M 자동 표시는 이번 실행에서 생략됩니다.
⚠ Gateway model cache could not be refreshed; the model picker may be stale.
2.1.220 (Claude Code)
# proxy endpoint timings, cold proxy (3s budget in cmdClaude)
claude-code #1 : 4771 ms
claude-code #2 : 23 ms
claude-code #3 : 18 ms
# and again after a 65s idle gap (BUNDLED_CATALOG_CACHE_MS = 60_000)
claude-code after 65s idle: 6325 ms
immediately after: 69 ms
# runtime resolution, same machine
resolveCodexRuntime({}) 1224 ms failures: 102
resolveCodexRuntime({ discoverAlternatives: false }) 60 ms
selected(true) : ...\Codex\bin\<hash>\codex.exe environment 0.146.0-alpha.3.1
selected(false): ...\Codex\bin\<hash>\codex.exe environment 0.146.0-alpha.3.1
newerAvailable : ...\npm\codex.cmd
Redacted configuration
{
"port": 10100,
"defaultProvider": "openai",
"codexAutoStart": true,
"providers": {
"openai": { "adapter": "openai-responses", "authMode": "forward", "codexAccountMode": "pool" },
"kiro": { "adapter": "kiro", "authMode": "oauth", "liveModels": false },
"kiro-native": { "adapter": "anthropic", "baseUrl": "http://127.0.0.1:3456", "allowPrivateNetwork": true, "liveModels": false },
"opencode-go": { "adapter": "openai-chat", "authMode": "key", "apiKey": "<redacted>" },
"xai": { "adapter": "openai-chat", "authMode": "oauth", "liveModels": true },
"google-antigravity": { "adapter": "google", "googleMode": "cloud-code-assist", "authMode": "oauth" }
}
}
Relevant environment: CODEX_CLI_PATH is set, so the runtime resolves with
source: "environment"; codex-cli is 0.146.0-alpha.14; the proxy runs under the bundled
Bun 1.3.14. No combos configured, disabledModels empty.
Checks
Unrelated aside, happy to split out if useful: model discovery for the adapter: "google" /
googleMode: "cloud-code-assist" provider fails with HTTP 404 and degrades to the static
catalog (Provider model discovery for "google-antigravity" failed with HTTP 404 [urlClass=provider-models, fallback=configured]).
Translated Message
Original language: Korean
Client or integration
Claude Code
Area
Proxy and routing
Summary
On Windows, ocx claude prints both of these on almost every launch:
⚠ Unable to load model context information — 1M auto marking will be skipped for this run.
⚠ Gateway model cache could not be refreshed; the model picker may be stale.
Neither is an auth or connectivity failure. Both are timeouts. cmdClaude gives
fetchClaudeContextWindows (/api/claude-code) and refreshGatewayModelCacheFromProxy
(/v1/models) a 3s budget each, and the local proxy takes 5-8s to answer. Both catch
blocks swallow the AbortError and emit the generic warning above.
The user-visible consequences are quiet but real: [1m] context marking is skipped for
the run, and ~/.claude/cache/gateway-models.json is never rewritten. Because Claude Code
only refreshes that cache itself when it holds a credential — and the subscription-preserving
launch deliberately injects none — the picker keeps serving whatever is on disk. Mine was
6 days stale (80 entries) before I traced this.
Expected: both endpoints answer within the 3s budget, so the cache is rewritten and [1m]
marking applies. Actual: both abort, every launch, and the warnings suggest an auth problem
rather than a latency one.
Root cause is a codex --version subprocess probe sitting on the request path, which is
effectively uncached for two independent reasons (details below).
Reproduction
-
Install @bitkyc08/opencodex 2.7.42 on Windows with a codex CLI present, plus several
additional codex launchers reachable via PATH (mine: npm global shim, a Codex
App-managed binary, and a shim backup — 102 probe candidates in total).
-
Start the proxy: ocx start --port 10100.
-
Run ocx claude --version.
-
Observe both warnings on stderr.
-
Time the two endpoints cmdClaude calls directly:
Measure-Command { Invoke-WebRequest 'http://127.0.0.1:10100/api/claude-code' -UseBasicParsing }
Measure-Command { Invoke-WebRequest 'http://127.0.0.1:10100/v1/models?limit=1000&ids=cli' -Headers @{'anthropic-version'='2023-06-01'} -UseBasicParsing }
Both exceed 3000 ms on a cold proxy. Note that repeating step 5 after a >60s idle gap
reproduces it again, because BUNDLED_CATALOG_CACHE_MS is 60s.
-
Confirm the effect: ~/.claude/cache/gateway-models.json keeps its old fetchedAt.
Timed in-process against the same config, to isolate where the time goes:
fetchAllModels 457 ms (all 7 providers, network included)
listCatalogNativeSlugs 1328 ms <-- no network at all
visibleNativeSlugs 1645 ms
nativeOpenAiSlugs 2072 ms
buildClaudeContextWindows 2018 ms
All of the native-slug cost is readCurrentCatalogOrCache() → loadBundledCodexCatalog().
The subprocess it wraps is not the problem:
codex debug models --bundled 87 ms
codex --version 721 ms
loadBundledCodexCatalog() three times in a row, nothing changing on disk, 60s cache:
#0 1214 ms
#1 1136 ms <-- expected ~0 on a warm cache
#2 1186 ms
Cause 1 — the bundled-catalog cache can never be hit
In src/codex/catalog/bundled.ts, candidates is computed in an IIFE that runs before
the bundledCatalogCache lookup, because cacheKey is assigned inside it. So
resolveAndPersistCodexRuntime() runs on every call, warm hits included. That would still
be cheap if the resolve were memoized, but line 154 forwards the real execFileSync:
const execFile = deps.execFileSync ?? (execFileSync as unknown as ExecFile);
// ...
const resolved = resolveAndPersistCodexRuntime({
execFileSync: execFile, // always truthy
and resolveCacheKey() in src/codex/runtime.ts deliberately refuses to memoize
injected-dependency resolves:
if (deps.execFileSync || deps.existsSync || deps.readFileSync || deps.configDir || deps.now) {
return null;
}
The guard reads correct in intent (injected deps imply a test double), but this caller
defeats it for the real-runtime path by passing its own already-defaulted value.
There is a related self-invalidation: resolveAndPersistCodexRuntime() (runtime.ts:505)
calls persistCodexRuntime unconditionally for any non-fallback selection, and
persistCodexRuntime() ends with resolveCache = null (runtime.ts:228). Since
persistedRuntimeCacheStamp() also folds the on-disk updatedAt into the cache key, each
write both clears the memo and changes the key it would have been stored under — even when
the resolved runtime is byte-identical to what is already persisted.
Cause 2 — cold reads probe every codex on PATH
bundled.ts:161 passes discoverAlternatives: deps.discoverAlternatives, i.e. undefined
for normal callers, which resolveCodexRuntimeUncached() treats as "discover". It then
probes every candidate to populate newerAvailable, each probe spawning codex --version
with a fresh mkdtempSync sandbox:
resolveCodexRuntime({}) 1224 ms failures: 102
resolveCodexRuntime({ discoverAlternatives: false }) 60 ms
Both select the identical runtime. Catalog loading only consumes resolved.runtime.command;
it never reads newerAvailable or failures. So on a cold read that ~1.2s scan is pure
overhead, and on its own eats more than a third of the 3s budget before any real work.
Effect
/api/claude-code measured over HTTP:
|
before |
after local patch |
| cold (fresh proxy) |
5000-8300 ms |
165 ms |
| warm |
6300 ms |
~25 ms |
| after 65s idle (cache expired) |
6325 ms |
1440 ms |
With the patches ocx claude runs clean, and gateway-models.json refreshed from 80 stale
entries to 98 current ones, 25 carrying [1m].
Patches I verified locally
Not opening a PR since you may prefer different tradeoffs, but these are what I tested.
src/codex/catalog/bundled.ts — forward an injected execFileSync only, so real-runtime
resolves stay memoizable:
...(deps.execFileSync ? { execFileSync: deps.execFileSync } : {}),
src/codex/catalog/bundled.ts — default the catalog path to non-discovering:
discoverAlternatives: deps.discoverAlternatives ?? false,
src/codex/runtime.ts — only persist when the selection actually changed, so the write
stops clearing its own memo:
const persistedRuntime = loadPersistedCodexRuntime(deps);
const selectionUnchanged = persistedRuntime !== null
&& persistedRuntime.command === result.runtime.command
&& persistedRuntime.source === result.runtime.source
&& (persistedRuntime.selectedVersion ?? null) === result.runtime.version;
if (result.runtime.command && result.runtime.source !== "fallback" && !selectionUnchanged) {
Tradeoff worth flagging on the second one: catalog-path callers lose newerAvailable
diagnostics. If something depends on those, moving discovery to a slow background interval
instead of every catalog read would preserve them while keeping the request path fast.
Suggested improvements beyond the fix
- Check
bundledCatalogCache before resolving the runtime. Deriving the cache key from
the very thing the cache exists to avoid means a warm hit can only ever save the
codex debug models call (87 ms), never the probe (1.2 s) that dominates. A cheaper key
would let warm hits skip both.
- Attribute the timeout in the warning text. Both messages read like auth or network
faults. Something like timed out after 3000ms would point straight at the cause; I went
looking at OAuth state and provider connectivity first.
- Consider raising or making the 3s budget configurable. Even fully patched, a cold read
is ~1.4s here, so a slower machine or a longer PATH could still trip it silently.
- A regression test that a repeated
loadBundledCodexCatalog() spawns no subprocess
would have caught both causes.
Version
2.7.42
Operating system
Windows 11
Provider and model
Not provider-specific. Reproduced with openai (forward), kiro, kiro-native,
opencode-go, xai, and google-antigravity configured; the slow path is local catalog
loading, not any upstream call.
Logs or error output
$ ocx claude --version
⚠ Unable to load model context information — 1M auto marking will be skipped for this run.
⚠ Gateway model cache could not be refreshed; the model picker may be stale.
2.1.220 (Claude Code)
# proxy endpoint timings, cold proxy (3s budget in cmdClaude)
claude-code #1 : 4771 ms
claude-code #2 : 23 ms
claude-code #3 : 18 ms
# and again after a 65s idle gap (BUNDLED_CATALOG_CACHE_MS = 60_000)
claude-code after 65s idle: 6325 ms
immediately after: 69 ms
# runtime resolution, same machine
resolveCodexRuntime({}) 1224 ms failures: 102
resolveCodexRuntime({ discoverAlternatives: false }) 60 ms
selected(true) : ...\Codex\bin\<hash>\codex.exe environment 0.146.0-alpha.3.1
selected(false): ...\Codex\bin\<hash>\codex.exe environment 0.146.0-alpha.3.1
newerAvailable : ...\npm\codex.cmd
Redacted configuration
{
"port": 10100,
"defaultProvider": "openai",
"codexAutoStart": true,
"providers": {
"openai": { "adapter": "openai-responses", "authMode": "forward", "codexAccountMode": "pool" },
"kiro": { "adapter": "kiro", "authMode": "oauth", "liveModels": false },
"kiro-native": { "adapter": "anthropic", "baseUrl": "http://127.0.0.1:3456", "allowPrivateNetwork": true, "liveModels": false },
"opencode-go": { "adapter": "openai-chat", "authMode": "key", "apiKey": "<redacted>" },
"xai": { "adapter": "openai-chat", "authMode": "oauth", "liveModels": true },
"google-antigravity": { "adapter": "google", "googleMode": "cloud-code-assist", "authMode": "oauth" }
}
}
Relevant environment: CODEX_CLI_PATH is set, so the runtime resolves with
source: "environment"; codex-cli is 0.146.0-alpha.14; the proxy runs under the bundled
Bun 1.3.14. No combos configured, disabledModels empty.
Checks
Unrelated aside, happy to split out if useful: model discovery for the adapter: "google" /
googleMode: "cloud-code-assist" provider fails with HTTP 404 and degrades to the static
catalog (Provider model discovery for "google-antigravity" failed with HTTP 404 [urlClass=provider-models, fallback=configured]).
Client or integration
Claude Code
Area
Proxy and routing
Summary
On Windows,
ocx claudeprints both of these on almost every launch:Neither is an auth or connectivity failure. Both are timeouts.
cmdClaudegivesfetchClaudeContextWindows(/api/claude-code) andrefreshGatewayModelCacheFromProxy(
/v1/models) a 3s budget each, and the local proxy takes 5-8s to answer. Bothcatchblocks swallow the
AbortErrorand emit the generic warning above.The user-visible consequences are quiet but real:
[1m]context marking is skipped forthe run, and
~/.claude/cache/gateway-models.jsonis never rewritten. Because Claude Codeonly refreshes that cache itself when it holds a credential — and the subscription-preserving
launch deliberately injects none — the picker keeps serving whatever is on disk. Mine was
6 days stale (80 entries) before I traced this.
Expected: both endpoints answer within the 3s budget, so the cache is rewritten and
[1m]marking applies. Actual: both abort, every launch, and the warnings suggest an auth problem
rather than a latency one.
Root cause is a
codex --versionsubprocess probe sitting on the request path, which iseffectively uncached for two independent reasons (details below).
Reproduction
Install
@bitkyc08/opencodex2.7.42 on Windows with acodexCLI present, plus severaladditional
codexlaunchers reachable viaPATH(mine: npm global shim, a CodexApp-managed binary, and a shim backup — 102 probe candidates in total).
Start the proxy:
ocx start --port 10100.Run
ocx claude --version.Observe both warnings on stderr.
Time the two endpoints
cmdClaudecalls directly:Both exceed 3000 ms on a cold proxy. Note that repeating step 5 after a >60s idle gap
reproduces it again, because
BUNDLED_CATALOG_CACHE_MSis 60s.Confirm the effect:
~/.claude/cache/gateway-models.jsonkeeps its oldfetchedAt.Timed in-process against the same config, to isolate where the time goes:
All of the native-slug cost is
readCurrentCatalogOrCache()→loadBundledCodexCatalog().The subprocess it wraps is not the problem:
loadBundledCodexCatalog()three times in a row, nothing changing on disk, 60s cache:Cause 1 — the bundled-catalog cache can never be hit
In
src/codex/catalog/bundled.ts,candidatesis computed in an IIFE that runs beforethe
bundledCatalogCachelookup, becausecacheKeyis assigned inside it. SoresolveAndPersistCodexRuntime()runs on every call, warm hits included. That would stillbe cheap if the resolve were memoized, but line 154 forwards the real
execFileSync:and
resolveCacheKey()insrc/codex/runtime.tsdeliberately refuses to memoizeinjected-dependency resolves:
The guard reads correct in intent (injected deps imply a test double), but this caller
defeats it for the real-runtime path by passing its own already-defaulted value.
There is a related self-invalidation:
resolveAndPersistCodexRuntime()(runtime.ts:505)calls
persistCodexRuntimeunconditionally for any non-fallback selection, andpersistCodexRuntime()ends withresolveCache = null(runtime.ts:228). SincepersistedRuntimeCacheStamp()also folds the on-diskupdatedAtinto the cache key, eachwrite both clears the memo and changes the key it would have been stored under — even when
the resolved runtime is byte-identical to what is already persisted.
Cause 2 — cold reads probe every
codexon PATHbundled.ts:161passesdiscoverAlternatives: deps.discoverAlternatives, i.e.undefinedfor normal callers, which
resolveCodexRuntimeUncached()treats as "discover". It thenprobes every candidate to populate
newerAvailable, each probe spawningcodex --versionwith a fresh
mkdtempSyncsandbox:Both select the identical runtime. Catalog loading only consumes
resolved.runtime.command;it never reads
newerAvailableorfailures. So on a cold read that ~1.2s scan is pureoverhead, and on its own eats more than a third of the 3s budget before any real work.
Effect
/api/claude-codemeasured over HTTP:With the patches
ocx clauderuns clean, andgateway-models.jsonrefreshed from 80 staleentries to 98 current ones, 25 carrying
[1m].Patches I verified locally
Not opening a PR since you may prefer different tradeoffs, but these are what I tested.
src/codex/catalog/bundled.ts— forward an injectedexecFileSynconly, so real-runtimeresolves stay memoizable:
src/codex/catalog/bundled.ts— default the catalog path to non-discovering:src/codex/runtime.ts— only persist when the selection actually changed, so the writestops clearing its own memo:
Tradeoff worth flagging on the second one: catalog-path callers lose
newerAvailablediagnostics. If something depends on those, moving discovery to a slow background interval
instead of every catalog read would preserve them while keeping the request path fast.
Suggested improvements beyond the fix
bundledCatalogCachebefore resolving the runtime. Deriving the cache key fromthe very thing the cache exists to avoid means a warm hit can only ever save the
codex debug modelscall (87 ms), never the probe (1.2 s) that dominates. A cheaper keywould let warm hits skip both.
faults. Something like
timed out after 3000mswould point straight at the cause; I wentlooking at OAuth state and provider connectivity first.
is ~1.4s here, so a slower machine or a longer
PATHcould still trip it silently.loadBundledCodexCatalog()spawns no subprocesswould have caught both causes.
Version
2.7.42
Operating system
Windows 11
Provider and model
Not provider-specific. Reproduced with
openai(forward),kiro,kiro-native,opencode-go,xai, andgoogle-antigravityconfigured; the slow path is local catalogloading, not any upstream call.
Logs or error output
Redacted configuration
{ "port": 10100, "defaultProvider": "openai", "codexAutoStart": true, "providers": { "openai": { "adapter": "openai-responses", "authMode": "forward", "codexAccountMode": "pool" }, "kiro": { "adapter": "kiro", "authMode": "oauth", "liveModels": false }, "kiro-native": { "adapter": "anthropic", "baseUrl": "http://127.0.0.1:3456", "allowPrivateNetwork": true, "liveModels": false }, "opencode-go": { "adapter": "openai-chat", "authMode": "key", "apiKey": "<redacted>" }, "xai": { "adapter": "openai-chat", "authMode": "oauth", "liveModels": true }, "google-antigravity": { "adapter": "google", "googleMode": "cloud-code-assist", "authMode": "oauth" } } }Relevant environment:
CODEX_CLI_PATHis set, so the runtime resolves withsource: "environment";codex-cliis0.146.0-alpha.14; the proxy runs under the bundledBun 1.3.14. No combos configured,
disabledModelsempty.Checks
Unrelated aside, happy to split out if useful: model discovery for the
adapter: "google"/googleMode: "cloud-code-assist"provider fails with HTTP 404 and degrades to the staticcatalog (
Provider model discovery for "google-antigravity" failed with HTTP 404 [urlClass=provider-models, fallback=configured]).Translated Message
Original language: Korean
Client or integration
Claude Code
Area
Proxy and routing
Summary
On Windows,
ocx claudeprints both of these on almost every launch:Neither is an auth or connectivity failure. Both are timeouts.
cmdClaudegivesfetchClaudeContextWindows(/api/claude-code) andrefreshGatewayModelCacheFromProxy(
/v1/models) a 3s budget each, and the local proxy takes 5-8s to answer. Bothcatchblocks swallow the
AbortErrorand emit the generic warning above.The user-visible consequences are quiet but real:
[1m]context marking is skipped forthe run, and
~/.claude/cache/gateway-models.jsonis never rewritten. Because Claude Codeonly refreshes that cache itself when it holds a credential — and the subscription-preserving
launch deliberately injects none — the picker keeps serving whatever is on disk. Mine was
6 days stale (80 entries) before I traced this.
Expected: both endpoints answer within the 3s budget, so the cache is rewritten and
[1m]marking applies. Actual: both abort, every launch, and the warnings suggest an auth problem
rather than a latency one.
Root cause is a
codex --versionsubprocess probe sitting on the request path, which iseffectively uncached for two independent reasons (details below).
Reproduction
Install
@bitkyc08/opencodex2.7.42 on Windows with acodexCLI present, plus severaladditional
codexlaunchers reachable viaPATH(mine: npm global shim, a CodexApp-managed binary, and a shim backup — 102 probe candidates in total).
Start the proxy:
ocx start --port 10100.Run
ocx claude --version.Observe both warnings on stderr.
Time the two endpoints
cmdClaudecalls directly:Both exceed 3000 ms on a cold proxy. Note that repeating step 5 after a >60s idle gap
reproduces it again, because
BUNDLED_CATALOG_CACHE_MSis 60s.Confirm the effect:
~/.claude/cache/gateway-models.jsonkeeps its oldfetchedAt.Timed in-process against the same config, to isolate where the time goes:
All of the native-slug cost is
readCurrentCatalogOrCache()→loadBundledCodexCatalog().The subprocess it wraps is not the problem:
loadBundledCodexCatalog()three times in a row, nothing changing on disk, 60s cache:Cause 1 — the bundled-catalog cache can never be hit
In
src/codex/catalog/bundled.ts,candidatesis computed in an IIFE that runs beforethe
bundledCatalogCachelookup, becausecacheKeyis assigned inside it. SoresolveAndPersistCodexRuntime()runs on every call, warm hits included. That would stillbe cheap if the resolve were memoized, but line 154 forwards the real
execFileSync:and
resolveCacheKey()insrc/codex/runtime.tsdeliberately refuses to memoizeinjected-dependency resolves:
The guard reads correct in intent (injected deps imply a test double), but this caller
defeats it for the real-runtime path by passing its own already-defaulted value.
There is a related self-invalidation:
resolveAndPersistCodexRuntime()(runtime.ts:505)calls
persistCodexRuntimeunconditionally for any non-fallback selection, andpersistCodexRuntime()ends withresolveCache = null(runtime.ts:228). SincepersistedRuntimeCacheStamp()also folds the on-diskupdatedAtinto the cache key, eachwrite both clears the memo and changes the key it would have been stored under — even when
the resolved runtime is byte-identical to what is already persisted.
Cause 2 — cold reads probe every
codexon PATHbundled.ts:161passesdiscoverAlternatives: deps.discoverAlternatives, i.e.undefinedfor normal callers, which
resolveCodexRuntimeUncached()treats as "discover". It thenprobes every candidate to populate
newerAvailable, each probe spawningcodex --versionwith a fresh
mkdtempSyncsandbox:Both select the identical runtime. Catalog loading only consumes
resolved.runtime.command;it never reads
newerAvailableorfailures. So on a cold read that ~1.2s scan is pureoverhead, and on its own eats more than a third of the 3s budget before any real work.
Effect
/api/claude-codemeasured over HTTP:With the patches
ocx clauderuns clean, andgateway-models.jsonrefreshed from 80 staleentries to 98 current ones, 25 carrying
[1m].Patches I verified locally
Not opening a PR since you may prefer different tradeoffs, but these are what I tested.
src/codex/catalog/bundled.ts— forward an injectedexecFileSynconly, so real-runtimeresolves stay memoizable:
src/codex/catalog/bundled.ts— default the catalog path to non-discovering:src/codex/runtime.ts— only persist when the selection actually changed, so the writestops clearing its own memo:
Tradeoff worth flagging on the second one: catalog-path callers lose
newerAvailablediagnostics. If something depends on those, moving discovery to a slow background interval
instead of every catalog read would preserve them while keeping the request path fast.
Suggested improvements beyond the fix
bundledCatalogCachebefore resolving the runtime. Deriving the cache key fromthe very thing the cache exists to avoid means a warm hit can only ever save the
codex debug modelscall (87 ms), never the probe (1.2 s) that dominates. A cheaper keywould let warm hits skip both.
faults. Something like
timed out after 3000mswould point straight at the cause; I wentlooking at OAuth state and provider connectivity first.
is ~1.4s here, so a slower machine or a longer
PATHcould still trip it silently.loadBundledCodexCatalog()spawns no subprocesswould have caught both causes.
Version
2.7.42
Operating system
Windows 11
Provider and model
Not provider-specific. Reproduced with
openai(forward),kiro,kiro-native,opencode-go,xai, andgoogle-antigravityconfigured; the slow path is local catalogloading, not any upstream call.
Logs or error output
Redacted configuration
{ "port": 10100, "defaultProvider": "openai", "codexAutoStart": true, "providers": { "openai": { "adapter": "openai-responses", "authMode": "forward", "codexAccountMode": "pool" }, "kiro": { "adapter": "kiro", "authMode": "oauth", "liveModels": false }, "kiro-native": { "adapter": "anthropic", "baseUrl": "http://127.0.0.1:3456", "allowPrivateNetwork": true, "liveModels": false }, "opencode-go": { "adapter": "openai-chat", "authMode": "key", "apiKey": "<redacted>" }, "xai": { "adapter": "openai-chat", "authMode": "oauth", "liveModels": true }, "google-antigravity": { "adapter": "google", "googleMode": "cloud-code-assist", "authMode": "oauth" } } }Relevant environment:
CODEX_CLI_PATHis set, so the runtime resolves withsource: "environment";codex-cliis0.146.0-alpha.14; the proxy runs under the bundledBun 1.3.14. No combos configured,
disabledModelsempty.Checks
Unrelated aside, happy to split out if useful: model discovery for the
adapter: "google"/googleMode: "cloud-code-assist"provider fails with HTTP 404 and degrades to the staticcatalog (
Provider model discovery for "google-antigravity" failed with HTTP 404 [urlClass=provider-models, fallback=configured]).