Client or integration
Codex App, Codex CLI, and any ACP client (observed via @agentclientprotocol/codex-acp inside Obsidian Copilot).
Area
Proxy and routing / Platform (Windows)
Summary
#1852 reported that the Windows catalog-state enumeration blocked the Bun event loop. #1876 fixed that: collectCodexAppServerCatalogStateForRequest() now uses listWindowsSnapshotsAsync() with in-flight dedup and a short cache, and /healthz stays responsive.
The latency half of the same code path is still there, and it is now the dominant cost of every multi-agent v2 turn. On 2.32.0 the enumeration still runs on the request hot path, and on this machine it takes ~7.5s — longer than CATALOG_STATE_TTL_MS, so the cache almost never serves a request.
Measured on this installation, same account, same trivial prompt ("reply with PONG"), 4 runs each, interleaved:
| Route |
median |
runs (ms) |
| through proxy |
11296 ms |
10844, 11245, 11296, 12689 |
| direct to ChatGPT (no proxy) |
5151 ms |
3818, 4654, 5151, 5836 |
through proxy, multiAgentGuidanceEnabled: false |
3424 ms |
3118, 3292, 3424, 5698 |
So the proxy was ~7s slower than no proxy at all; with guidance off it is faster than direct (the WS upstream transport pays off as documented in ws-upstream.ts).
Evidence that the cost is the catalog probe, not the network
- The proxy's own
usage.jsonl already shows it. Representative slow row:
durationMs: 10175, firstOutputMs: 9969
attempts: [{ ordinal: 1, adapter: "openai-responses", status: 200,
durationMs: 2024, firstOutputMs: 1818, sendCount: 1, recoveryKinds: [] }]
The upstream attempt took 2.0s; the request took 10.2s. ~8s is spent before the attempt starts.
-
CPU time of the proxy process during those 8 seconds is 0–31 ms per 900 ms sample — it is waiting on a child process, not computing.
-
Temporary console.log timestamps in src/server/responses/core.ts localise it exactly:
core: enter t0
step: before body read t0 +8 ms
step: before route normalization t0 +17 ms
step: before codex auth t0 +7492 ms <-- applyFinalRouteRequestNormalization
step: before request build t0 +7497 ms
step: before fetch t0 +7501 ms
providerFetch -> WS upstream t0 +7502 ms
wsUpstream OPEN +346 ms
first frame to client +498 ms
applyFinalRouteRequestNormalization() → multiAgentGuidanceText() → defaultCollectCatalogState() is the whole delay. With multiAgentGuidanceEnabled: false the same steps take 1–2 ms and durationMs equals attempts[0].durationMs within 4–8 ms.
Root cause: GetOwner is per-process, and the TTL is shorter than the probe
Running the exact script from windowsSnapshotPowerShellCommand() standalone:
snapshot #1: 5617 ms, 9 rows
snapshot #2: 5909 ms, 9 rows
snapshot #3: 5893 ms, 10 rows
Split into its two parts (822 processes on this machine):
| Step |
Cost |
Get-CimInstance Win32_Process + CommandLine regex filter |
646 ms |
Invoke-CimMethod GetOwner, 93 candidate processes |
43887 ms (~472 ms per process) |
So the snapshot cost scales with the number of running Codex processes, not with machine size. This installation runs 9–10 of them at once — desktop app-server, plugin app-server, codex sandbox, codex-code-mode-host, codex-command-runner — which is an ordinary Codex App session, giving ~5.9s; the batched start-time query brings the total to ~7.5s.
That interacts badly with the cache:
const CATALOG_STATE_TTL_MS = 5_000; // app-server-processes.ts
const CATALOG_STATE_UNKNOWN_TTL_MS = 250;
A 5s TTL in front of a 7.5s probe cannot help: the entry is stale before the next turn starts. In-flight dedup only merges concurrent turns, and sequential chat turns are not concurrent. The comment above collectCodexAppServerCatalogStateForRequest ("Typical cold cost is tens of milliseconds") does not hold on Windows once more than one or two Codex processes are alive.
Suggestion 5 from #1852 ("optionally narrow the CIM query before owner lookups") was reasonably deferred there, because narrowing does not fix event-loop blocking. Now that the blocking is fixed, that suggestion is the remaining problem.
Suggested safe direction
- Drop the per-process
GetOwner fan-out. One Get-CimInstance Win32_Process already returns everything needed except the owner; owner can come from a single association query, or from a SessionId/token comparison, or by dropping owner filtering to a cheaper heuristic and treating ambiguity as unknown. This alone should turn ~5.9s into <1s.
- Serve stale while revalidating. The probe result is advisory. Returning the last known state immediately and refreshing in the background would remove it from the hot path entirely, instead of making one unlucky turn per TTL window pay the full cost.
- Make the TTL exceed the observed probe cost (or adapt it to the measured duration). A TTL below the probe duration guarantees a miss on every sequential turn.
- Regression test: with the enumeration seam stubbed to take, say, 3s and a TTL of 5s, assert that N sequential guidance builds trigger at most one enumeration and that turns after the first do not wait on it.
Workaround
ocx agent injection set --guidance off
(equivalently "multiAgentGuidanceEnabled": false in ~/.opencodex/config.json). Median turn dropped from 11296 ms to 3424 ms. This only disables OpenCodex's extra guidance injection; syncCodexSubagentDefaults and Codex's own collaboration tools are unaffected.
Note that this is the effective default for every user, since multiAgentGuidanceEnabled(config) returns config.multiAgentGuidanceEnabled !== false.
Version
OpenCodex 2.32.0 (verified byte-identical to the published npm tarball), bundled Bun 1.4.0, Codex CLI 0.149.0.
Operating system
Windows 11 Home, version 10.0.26200 (build 26200). Native Windows, no WSL.
Provider and model
Not provider-specific. Reproduced on openai/gpt-5.6-sol with ChatGPT login (codexAccountMode: pool); the delay happens before any provider work.
Logs or error output
usage.jsonl (slow turn) durationMs=10175 firstOutputMs=9969
attempts[0].durationMs=2024 firstOutputMs=1818
proxy process CPU during the 8s gap: 0-31 ms per 900 ms sample
standalone snapshot script: 5617 / 5909 / 5893 ms
GetOwner alone, 93 processes: 43887 ms
Screenshots and supporting files
Not required; timings and code paths above are text-reproducible.
Redacted configuration
{
"multiAgentGuidanceEnabled": true,
"injectionModel": "gpt-5.6-sol",
"injectionEffort": "xhigh"
}
Related issues
Checks
Client or integration
Codex App, Codex CLI, and any ACP client (observed via
@agentclientprotocol/codex-acpinside Obsidian Copilot).Area
Proxy and routing / Platform (Windows)
Summary
#1852 reported that the Windows catalog-state enumeration blocked the Bun event loop. #1876 fixed that:
collectCodexAppServerCatalogStateForRequest()now useslistWindowsSnapshotsAsync()with in-flight dedup and a short cache, and/healthzstays responsive.The latency half of the same code path is still there, and it is now the dominant cost of every multi-agent v2 turn. On 2.32.0 the enumeration still runs on the request hot path, and on this machine it takes ~7.5s — longer than
CATALOG_STATE_TTL_MS, so the cache almost never serves a request.Measured on this installation, same account, same trivial prompt ("reply with PONG"), 4 runs each, interleaved:
multiAgentGuidanceEnabled: falseSo the proxy was ~7s slower than no proxy at all; with guidance off it is faster than direct (the WS upstream transport pays off as documented in
ws-upstream.ts).Evidence that the cost is the catalog probe, not the network
usage.jsonlalready shows it. Representative slow row:The upstream attempt took 2.0s; the request took 10.2s. ~8s is spent before the attempt starts.
CPU time of the proxy process during those 8 seconds is 0–31 ms per 900 ms sample — it is waiting on a child process, not computing.
Temporary
console.logtimestamps insrc/server/responses/core.tslocalise it exactly:applyFinalRouteRequestNormalization()→multiAgentGuidanceText()→defaultCollectCatalogState()is the whole delay. WithmultiAgentGuidanceEnabled: falsethe same steps take 1–2 ms anddurationMsequalsattempts[0].durationMswithin 4–8 ms.Root cause:
GetOwneris per-process, and the TTL is shorter than the probeRunning the exact script from
windowsSnapshotPowerShellCommand()standalone:Split into its two parts (822 processes on this machine):
Get-CimInstance Win32_Process+CommandLineregex filterInvoke-CimMethod GetOwner, 93 candidate processesSo the snapshot cost scales with the number of running Codex processes, not with machine size. This installation runs 9–10 of them at once — desktop app-server, plugin app-server,
codex sandbox,codex-code-mode-host,codex-command-runner— which is an ordinary Codex App session, giving ~5.9s; the batched start-time query brings the total to ~7.5s.That interacts badly with the cache:
A 5s TTL in front of a 7.5s probe cannot help: the entry is stale before the next turn starts. In-flight dedup only merges concurrent turns, and sequential chat turns are not concurrent. The comment above
collectCodexAppServerCatalogStateForRequest("Typical cold cost is tens of milliseconds") does not hold on Windows once more than one or two Codex processes are alive.Suggestion 5 from #1852 ("optionally narrow the CIM query before owner lookups") was reasonably deferred there, because narrowing does not fix event-loop blocking. Now that the blocking is fixed, that suggestion is the remaining problem.
Suggested safe direction
GetOwnerfan-out. OneGet-CimInstance Win32_Processalready returns everything needed except the owner; owner can come from a single association query, or from aSessionId/token comparison, or by dropping owner filtering to a cheaper heuristic and treating ambiguity asunknown. This alone should turn ~5.9s into <1s.Workaround
(equivalently
"multiAgentGuidanceEnabled": falsein~/.opencodex/config.json). Median turn dropped from 11296 ms to 3424 ms. This only disables OpenCodex's extra guidance injection;syncCodexSubagentDefaultsand Codex's own collaboration tools are unaffected.Note that this is the effective default for every user, since
multiAgentGuidanceEnabled(config)returnsconfig.multiAgentGuidanceEnabled !== false.Version
OpenCodex 2.32.0 (verified byte-identical to the published npm tarball), bundled Bun 1.4.0, Codex CLI 0.149.0.
Operating system
Windows 11 Home, version 10.0.26200 (build 26200). Native Windows, no WSL.
Provider and model
Not provider-specific. Reproduced on
openai/gpt-5.6-solwith ChatGPT login (codexAccountMode: pool); the delay happens before any provider work.Logs or error output
Screenshots and supporting files
Not required; timings and code paths above are text-reproducible.
Redacted configuration
{ "multiAgentGuidanceEnabled": true, "injectionModel": "gpt-5.6-sol", "injectionEffort": "xhigh" }Related issues
unknownTTLChecks