BigPizzaV3/CodexPlusPlus · warning · anyhow::Error

failed to wait for Windows process id {process_id}

Error message

failed to wait for Windows process id {process_id}

What it means

wait_for_windows_process_id_blocking (crates/codex-plus-core/src/launcher.rs:2964, Windows-only) opens the child process with PROCESS_SYNCHRONIZE and blocks in WaitForSingleObject(handle, INFINITE) to observe exit. If the wait returns WAIT_FAILED — after OpenProcess itself succeeded — it bails with this message. WAIT_FAILED means the wait itself errored (invalid handle state, waiting on an object that cannot be waited on, or the handle was invalidated), which is distinct from a timeout (impossible with INFINITE) or a clean signal.

Source

Thrown at crates/codex-plus-core/src/launcher.rs:2981

#[cfg(windows)]
fn wait_for_windows_process_id_blocking(process_id: u32) -> anyhow::Result<()> {
    use windows::Win32::Foundation::{CloseHandle, WAIT_FAILED};
    use windows::Win32::System::Threading::{
        INFINITE, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE,
        WaitForSingleObject,
    };

    unsafe {
        let handle = OpenProcess(
            PROCESS_SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
            false,
            process_id,
        )
        .with_context(|| format!("failed to open Windows process id {process_id}"))?;
        let wait_result = WaitForSingleObject(handle, INFINITE);
        let _ = CloseHandle(handle);
        if wait_result == WAIT_FAILED {
            anyhow::bail!("failed to wait for Windows process id {process_id}");
        }
    }
    Ok(())
}

#[cfg(windows)]
fn terminate_windows_process_id_blocking(process_id: u32) -> anyhow::Result<()> {
    use windows::Win32::Foundation::CloseHandle;
    use windows::Win32::System::Threading::{
        OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_TERMINATE, TerminateProcess,
    };

    unsafe {
        let handle = OpenProcess(
            PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION,
            false,
            process_id,
        )

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Treat as best-effort: the caller only waits to observe exit — catch the error, verify liveness with a fallback (process enumeration or polling) instead of failing the launch
  2. Check whether the child actually exited (exit code via GetExitCodeProcess) — often the process is already gone and the wait is moot
  3. Reproduce with the pid: OpenProcess(PROCESS_SYNCHRONIZE, ...) + WaitForSingleObject in a scratch program to see whether AV or handle inheritance is interfering
  4. If persistent in your environment, fork to use RegisterWaitForSingleObject or a job object (AssignProcessToJobObject) for robust child tracking

Example fix

// before: any wait failure aborts the caller
wait_for_windows_process_id(pid).await?;

// after: wait is advisory — degrade to a liveness check
if let Err(error) = wait_for_windows_process_id(pid).await {
    tracing::warn!(%pid, %error, "process wait failed; falling back to liveness polling");
    while process_still_alive(pid) {
        tokio::time::sleep(Duration::from_millis(250)).await;
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

#[cfg(windows)]
fn can_open_for_wait(process_id: u32) -> bool {
    use windows::Win32::System::Threading::{OpenProcess, PROCESS_SYNCHRONIZE};
    unsafe { OpenProcess(PROCESS_SYNCHRONIZE, false, process_id).is_ok() }
}

Try / catch

if let Err(error) = wait_for_windows_process_id(pid).await {
    tracing::warn!(pid, %error, "wait failed; falling back to liveness polling");
    while process_alive(pid) { tokio::time::sleep(Duration::from_millis(250)).await; }
}

Prevention

When it happens

Trigger: Launch flow on Windows where the spawned Codex process handle fails WaitForSingleObject: process terminated and handle already signalled-invalidated in edge cases, handle closed by antivirus/injection tooling, or the pid reused pointing at a non-waitable system pseudo-process.

Common situations: Security software tampering with child process handles; spawning then immediately killing the process so the wait hits a dead handle race; passing a pid of a system process or a stale pid after restart; heavy load causing the child to die between spawn and wait.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/f53572ba51ed5620. Report an issue: GitHub.