Skip to content

[Bug][Windows]: synchronous PowerShell app-server enumeration blocks /healthz and proxy traffic on v2 requests #1852

Description

@Hylouis233

Client or integration

Codex App

Area

Proxy and routing / Platform (Windows)

Summary

On Windows, OpenCodex performs synchronous PowerShell/CIM process enumeration on the proxy's main Bun event loop while building multi-agent v2 guidance.

The hot request path is:

  1. buildMultiAgentGuidance() handles a v2 surface.
  2. It awaits defaultCollectCatalogState().
  3. That calls the synchronous collectCodexAppServerCatalogState().
  4. listWindowsSnapshots() runs execFileSync(powershell.exe, ..., timeout: 8_000).

Relevant source:

On the affected machine, one enumeration blocks the Bun event loop for 3.8-5.1 seconds. During that interval, even loopback /healthz cannot be serviced. Under repeated v2 requests, these pauses can queue and make the proxy appear permanently hung: the Bun process remains alive and owns port 10100, but health probes time out and connections accumulate in CLOSE_WAIT.

I expected catalog-state discovery to be asynchronous, deduplicated/cached, or outside the request hot path so a slow CIM query cannot block unrelated proxy traffic and health checks.

This remains present in the latest stable package, 2.22.0, and in current main.

Reproduction

With the repository dependencies available on Windows, run the following with Bun:

const { listWindowsSnapshots } =
  await import("./src/codex/app-server-processes.ts");

for (let i = 1; i <= 5; i++) {
  const scheduled = performance.now();
  const timer = new Promise<number>(resolve =>
    setTimeout(() => resolve(performance.now() - scheduled), 10)
  );

  const started = performance.now();
  let count: number | null = null;
  let error: string | null = null;
  try {
    count = listWindowsSnapshots().length;
  } catch (e) {
    error = e instanceof Error ? e.message : String(e);
  }

  const callMs = performance.now() - started;
  const timerMs = await timer;
  console.log({ run: i, callMs, timerMs, count, error });
}

A 10 ms timer is delayed by essentially the full synchronous call duration:

Run listWindowsSnapshots 10 ms timer fired after Result
1 3812 ms 3813 ms 3 processes
2 3937 ms 3943 ms 3 processes
3 4080 ms 4082 ms 3 processes
4 5072 ms 5075 ms windows_enum_incomplete
5 4402 ms 4406 ms 3 processes

The timer evidence makes the event-loop blocking deterministic without requiring an upstream provider request.

For the live proxy symptom:

  1. Leave multiAgentGuidanceEnabled at its effective default (true).
  2. Use Codex App with the multi-agent v2 surface and issue repeated turns/tool continuations.
  3. Probe http://127.0.0.1:10100/healthz concurrently.
  4. Observe a PowerShell child under the OpenCodex Bun process running the Get-CimInstance Win32_Process / GetOwner script.
  5. Health requests intermittently take several seconds or time out despite the listener and Bun PID remaining alive.
  6. Setting multiAgentGuidanceEnabled: false removes this request-path enumeration. In the observed session, two post-change probe batches passed 45/45 and no enumeration child remained.

A live stalled sample before the workaround showed:

  • /healthz: 5-second timeout, zero response bytes
  • Bun CPU increase over 2 seconds: 0.016 s (waiting, not spinning)
  • Bun private memory: about 1.69 GiB
  • listener still active on port 10100
  • multiple CLOSE_WAIT connections
  • a PowerShell child running the catalog process enumeration

These are observations from one affected installation; the deterministic timer reproduction above is the primary evidence.

Root cause

The async wrapper does not make the synchronous collector non-blocking. execFileSync prevents Bun from advancing timers, accepting/servicing health requests, or processing other proxy work until PowerShell exits or reaches the 8-second timeout.

The enumeration is especially expensive because it walks Win32_Process and calls GetOwner for candidates. Issue #1354 already measured multi-second Windows enumeration and fixed a correctness race in the same collector, but did not remove it from the event loop or request hot path.

This is analogous to #612 / #645, where synchronous icacls work blocked /healthz until that high-frequency path was made asynchronous.

Suggested safe direction

  1. Use asynchronous process creation for Windows enumeration, or move it to a worker so the proxy event loop remains responsive.
  2. Deduplicate concurrent discovery with a single in-flight operation.
  3. Cache the last result with a short TTL / stale-while-revalidate policy. Catalog-state guidance is advisory; serving a recent/unknown state is safer than blocking all proxy traffic.
  4. Add a regression test that schedules a short timer or health request while the process-enumeration seam is deliberately slow and verifies it is serviced before enumeration completes.
  5. Optionally narrow the CIM query before owner lookups, but query optimization alone would not remove the event-loop blocking contract.

Workaround

{
  "multiAgentGuidanceEnabled": false
}

This disables OpenCodex's additional multi-agent guidance/roster injection, not Codex's built-in collaboration tools.

Version

OpenCodex 2.22.0, bundled Bun 1.3.14, Codex CLI 0.147.0.

Operating system

Windows 11 Pro, version 10.0.26200 (build 26200).

Provider and model

Not provider-specific. The blocking occurs before provider work while building v2 guidance.

Logs or error output

GET /healthz -> timeout after 5.0s, 0 bytes
PowerShell child -> Get-CimInstance Win32_Process ... Invoke-CimMethod GetOwner
listWindowsSnapshots() -> 3.8-5.1s synchronous wait

Screenshots and supporting files

Not required; the source links, deterministic timer reproduction, timings, and live probe observations are text-reproducible.

Redacted configuration

{
  "multiAgentGuidanceEnabled": true
}

Related issues

Checks

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions