BigPizzaV3/CodexPlusPlus · error

Previous launcher has not exited; no process was forcibly…

Error message

Previous launcher has not exited; no process was forcibly terminated

What it means

During a launcher restart on Windows, the manager captures a snapshot of the previous launcher process incarnations (pid + birth id) and waits for them to exit. wait_for_exit polls until the deadline; if the processes are still alive (matched by birth id so PID reuse does not fool it) when the timeout expires, it fails. The message also clarifies that no forceful termination was performed by this wait path — it only observed non-exit.

Solutions

  1. Increase the wait timeout passed to wait_for_exit (or RESTART_STOP_WAIT_TIMEOUT_MS) and retry the restart
  2. Manually kill the stale launcher process (Task Manager / taskkill /PID <pid>) and retry
  3. Check whether the process is blocked on a modal dialog or file lock; close it and rerun
  4. Verify the process is actually the previous incarnation (birth id check) and not an unrelated process that happens to match the killable filter

Example fix

// before
snapshot.wait_for_exit(Duration::from_secs(2))?;
// after
snapshot.wait_for_exit(Duration::from_secs(30))?;
Defensive patterns

Strategy: try-catch

Validate before calling

// before restarting, check whether a stale launcher is still alive
let snapshot = LauncherExitSnapshot::capture()?;
if !snapshot.wait_for_exit(Duration::from_secs(0)).is_ok() {
    eprintln!("stale launcher still running; kill it manually before restart");
}

Try / catch

match snapshot.wait_for_exit(Duration::from_secs(30)) {
    Ok(()) => proceed_with_restart(),
    Err(e) if e.to_string().contains("not exited") => {
        // prompt user to close/kill the stale launcher, then retry once
        prompt_and_retry_with_longer_timeout();
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling LauncherExitSnapshot::wait_for_exit(timeout) after stop_launcher_processes_and_wait when the previous launcher process does not terminate within RESTART_STOP_WAIT_TIMEOUT_MS; each poll finds launcher_incarnations_still_running non-empty until Instant::now() >= deadline.

Common situations: The old launcher process is hung, blocked on an unsaved dialog, stuck in a blocking I/O call, or antivirus/software is preventing termination; very short timeout configured on a slow machine; the killable-filter skipped the actual parent process so it was never asked to exit.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/506af55eb64245b7. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/watcher.rs:584

                captured.push((pid, birth));
            } else {
                anyhow::bail!("Cannot verify launcher process identity: {pid}");
            }
        }
        Ok(Self { processes: captured })
    }

    pub fn wait_for_exit(self, timeout: Duration) -> anyhow::Result<()> {
        let deadline = std::time::Instant::now() + timeout;
        loop {
            let remaining = launcher_incarnations_still_running(
                &self.processes,
                |pid| crate::windows_integration::process_birth_id(pid),
            );
            if remaining.is_empty() {
                return Ok(());
            }
            anyhow::ensure!(
                std::time::Instant::now() < deadline,
                "Previous launcher has not exited; no process was forcibly terminated"
            );
            std::thread::sleep(Duration::from_millis(RESTART_STOP_WAIT_INTERVAL_MS));
        }
    }
}

#[cfg(any(windows, test))]
fn launcher_incarnations_still_running(
    captured: &[(u32, u64)],
    mut birth_id: impl FnMut(u32) -> Option<u64>,
) -> Vec<u32> {
    captured.iter().filter_map(|(pid, birth)| {
        (birth_id(*pid) == Some(*birth)).then_some(*pid)
    }).collect()
}

View on GitHub (pinned to b1ed92e5e4)