fix(win): adapt desktop-host discovery to the Codex → ChatGPT app merge - #54
Conversation
The merged Windows host runs as ChatGPT.exe (MSIX package family
unchanged: OpenAI.Codex_2p2nqsd0c76g0; non-Store installs under
%LOCALAPPDATA%\Programs\ChatGPT), so the Codex.exe-only tasklist /
taskkill checks missed the running UI: switch skipped quitting the
host, leaving its in-memory app-server on the old account, and never
relaunched it.
- is-running / quit enumerate both host names via one Win32_Process
CIM query and classify each PID by executable install identity:
OpenAI.Codex* package family (incl. beta) or a non-Store install
embedding resources\codex.exe => ours; ChatGPT Classic
(OpenAI.ChatGPT-Desktop_*, same exe name) => positively excluded;
unattributable => counts as running, never signalled (mirrors the
macOS ours/other/unknown semantics)
- quit signals by PID (graceful taskkill /PID, then /F) instead of
/IM <name>, with the differentiated APP_EXIT_FAILED message
- launch prefers the detected Store package (shell:AppsFolder id is
unchanged post-merge), then a qualified non-Store executable
- CLI discovery + Settings auto-detect gain the desktop-bundled CLI
tiers: <MSIX InstallLocation>\app\resources\codex.exe and
Programs\{ChatGPT,Codex}\resources\codex.exe
Facts anchored from public sources (winget manifests, KAPE targets,
third-party Codex tooling paths); pending verification on a live
Windows install — noted in CHANGELOG.
…beta coverage Findings from the pre-push review pass (1 BLOCKER, 4 MEDIUM, 1 LOW): - BLOCKER: reopen_codex_app_if_needed's match was non-exhaustive after AppLaunchTarget gained Executable — E0004, the whole win module (and the Linux CI job that compiles it) failed to build. Added the Executable arm so non-Store installs relaunch too. - both PowerShell-backed OnceLock caches (Store shell target, MSIX InstallLocation) no longer pin a transient probe failure as 'absent': they cache only an authoritative answer and retry otherwise, so a login-storm spawn failure can't wedge the bundled CLI candidate for the whole session. - tasklist is no longer the sole short-circuit: a broken tasklist now returns Unavailable (not 'not running'), and is_codex_app_running falls through to PowerShell classification instead of silently skipping the host quit (the switch-time contamination direction). Both probe failures log once via OnceLock sentinels. - Get-AppxPackage widened to 'OpenAI.Codex*' and the beta standalone process name 'Codex (Beta).exe' added to enumeration, so the beta channel is actually covered rather than only named in comments. - quit's could-not-attribute message now hints at PowerShell being blocked; the wait loop uses classification directly and documents the ~7-12s worst-case latency.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1cdaf6f06b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // name probe and stay conservative — a bare name match counts | ||
| // as running so the switch aborts instead of proceeding over a | ||
| // possibly-live host (quit refuses to signal it). | ||
| return matches!(host_name_probe(), NameProbe::Present); |
There was a problem hiding this comment.
Treat an unavailable fallback as still running
When PowerShell classification returns no rows because the probe failed, this fallback only treats a second tasklist result of Present as running. If tasklist is also unavailable (NameProbe::Unavailable), the function returns false even though that state is explicitly unknown, so is_codex_app_running can proceed with a switch over a live host in the exact degraded environment this path is meant to handle. The conservative branch should include Unavailable as running/abort-worthy.
Useful? React with 👍 / 👎.
| return Vec::new(); | ||
| } | ||
| }; | ||
| parse_pid_classification_lines(&String::from_utf8_lossy(&output.stdout)) |
There was a problem hiding this comment.
Emit UTF-8 from PowerShell before parsing paths
This decodes the CIM output as UTF-8, but Windows PowerShell commonly writes redirected text using the active OEM code page. For non-Store installs under a non-ASCII %LOCALAPPDATA% path, the executable path is mojibaked before host_dir_embeds_codex_cli checks for resources\codex.exe; the PID becomes Unknown, which still blocks is_codex_app_running but is never signalled, so every switch aborts while that app is running. Set PowerShell's output encoding to UTF-8 (or decode the console code page) before parsing paths.
Useful? React with 👍 / 👎.
| // Desktop-host bundled CLI as the last tier — mirrors the macOS | ||
| // app-bundle fallback so a machine whose only codex is the one | ||
| // inside the (merged) desktop app still resolves. | ||
| for candidate in desktop_host_bundled_cli_candidates() { |
There was a problem hiding this comment.
Probe bundled CLI candidates before saving them
When no normal CLI is found, this new fallback accepts the desktop-bundled resources\codex.exe solely because the file exists and then resolve_real_codex_cli_with_source persists it without the --version runnable probe used by Settings redetect. On MSIX installs where that exact packaged path exists but cannot be launched directly from outside the package (for example, upstream reports show WindowsApps\OpenAI.Codex...\app\resources\codex.exe failing with Access denied: openai/codex#31086), login and app-server refreshes will fail with a launch error instead of continuing discovery or reporting no usable CLI. Gate this fallback with the same runnable check before returning it.
Useful? React with 👍 / 👎.
| // macOS `Contents/Resources/codex` qualifier). Classic has no such | ||
| // file, but its absence alone cannot rule a host out (probe | ||
| // failures), so it stays Unknown rather than Other. | ||
| if host_dir_embeds_codex_cli(Path::new(executable_path)) { |
There was a problem hiding this comment.
Recognize non-Store app-server children
For non-Store installs the desktop host's bundled child runs from %LOCALAPPDATA%\Programs\ChatGPT\resources\codex.exe, and the CIM filter above matches that process via Name='Codex.exe'. Classifying that child here calls host_dir_embeds_codex_cli on the child executable, which looks for resources\resources\codex.exe and returns false; the child becomes Unknown, which keeps is_codex_host_running_via_classification true but is never signalled, so a switch can time out after ChatGPT.exe exits whenever the app-server child lingers.
Useful? React with 👍 / 👎.
- bump version 1.6.0 -> 1.6.1 (package.json / Cargo.toml / locks) - CHANGELOG: promote Unreleased to 1.6.1 - 2026-07-12 - README (zh/en): refresh version:set example - release-notes/v1.6.1.md: bilingual notes (Windows ChatGPT merge adaptation, #54)
Summary
The Windows counterpart of the macOS adaptation shipped in v1.6.0 (#51). OpenAI folded the Codex desktop app into the ChatGPT desktop app on Windows too on 2026-07-09 (confirmed via the OpenAI help center, changelog, winget manifests and third-party tooling). The merged host runs as
ChatGPT.exewhile the MSIX package family staysOpenAI.Codex_2p2nqsd0c76g0, so the oldCodex.exe-onlytasklist/taskkillchecks miss the running UI: switching skips quitting the host (leaving its in-memory app-server on the old account — the cross-contamination vector) and never relaunches it.Anchored facts (no live Windows box — sourced from public evidence)
ChatGPT.exe; non-Store install under%LOCALAPPDATA%\Programs\ChatGPT\(EchoBirdchatgptdesktop/paths.json, file.net)OpenAI.Codex_2p2nqsd0c76g0unchanged across the in-place update (wingetOpenAI.Codex0.143.0, ReleaseDate 2026-07-08; OpenCodex asar scanner; "update as usual" in the OpenAI help center)OpenAI.ChatGPT-Desktop_2p2nqsd0c76g0whose executable is alsoChatGPT.exe(KAPEChatGPT.tkape)<InstallLocation>\app\resources\codex.exe(codex-windows-fast-patch-skill), the Windows analog of macOSContents/Resources/codexChanges
Win32_ProcessCIM query and classify each PID by its executable install identity: theOpenAI.Codex*package family (incl. beta) or a non-Store install embeddingresources\codex.exe→ ours; ChatGPT Classic → positively excluded, never killed; unattributable → counts as running (switch aborts) but never signalled. Same ours/other/unknown semantics as the macOS side.taskkill /PIDthen/F) instead of/IM <name>, so a co-running ChatGPT Classic is untouched.shell:AppsFolderid), then a qualified non-StoreChatGPT.exe/Codex.exe.<InstallLocation>\app\resources\codex.exeand non-StorePrograms\{ChatGPT,Codex}\resources\codex.exe).tasklistfalls through to classification rather than silently reporting "not running"; both probe failures log once.Verification
cargo test --lib: 137 passed / 0 failed (4 new win-only tests: MSIX/Classic/beta classification, bundled-CLI detection, CIM-line parsing, process-name set). The win module compiles only on the Linuxcargo test --libCI job — that is the real compile gate here.cargo fmt --all --check,cargo macheteclean.tasklist/Get-AppxPackage OpenAI.*/ package-dir check on a merged install would close that.🤖 Generated with Claude Code