Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/codex-plus-core/src/app_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ pub fn codex_app_version(app_dir: &Path) -> Option<String> {
}

pub fn packaged_app_user_model_id(app_dir: &Path) -> Option<String> {
// An unpacked/portable MSIX keeps the package-shaped directory name, but it is
// not registered with Windows and therefore cannot be started through
// IApplicationActivationManager. Only genuine WindowsApps installs should use
// packaged activation; portable copies must launch ChatGPT.exe/Codex.exe
// directly.
if !path_is_inside_windows_apps(app_dir) {
return None;
}
let package_name = package_name_from_app_dir(app_dir)?;
let (spec, _, publisher_id) = codex_package_parts(&package_name)?;
if publisher_id.is_empty() {
Expand All @@ -291,6 +299,13 @@ pub fn packaged_app_user_model_id(app_dir: &Path) -> Option<String> {
Some(format!("{}_{publisher_id}!{}", spec.identity, spec.app_id))
}

fn path_is_inside_windows_apps(path: &Path) -> bool {
path.to_string_lossy()
.replace('\\', "/")
.split('/')
.any(|part| part.eq_ignore_ascii_case("WindowsApps"))
}

fn package_name_from_app_dir(app_dir: &Path) -> Option<String> {
let path = app_dir.to_string_lossy().replace('\\', "/");
let mut parts = path.split('/').filter(|part| !part.is_empty());
Expand Down
9 changes: 4 additions & 5 deletions crates/codex-plus-core/src/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,11 @@ pub fn find_codex_processes_from_snapshot(
.map(|(pid, path)| (*pid, path.as_str())),
);

// Local/portable installs use Codex.exe as the Electron main process. Do not match
// lowercase codex.exe here; that is commonly the CLI binary. ChatGPT.exe is accepted
// only for packaged Store apps above, because the standalone ChatGPT app can be a
// normal ChatGPT session rather than Codex.
// Local/portable installs use Codex.exe or, since Codex was merged into the ChatGPT
// desktop app, ChatGPT.exe as the Electron main process. Do not match lowercase
// codex.exe here; that is commonly the CLI binary.
for process in processes {
if process.exe_file == "Codex.exe" {
if process.exe_file == "Codex.exe" || process.exe_file == "ChatGPT.exe" {
ids.push(process.process_id);
}
}
Expand Down
19 changes: 17 additions & 2 deletions crates/codex-plus-core/tests/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,24 @@ fn app_paths_normalizes_chatgpt_desktop_executable_and_builds_it() {
Some(app.as_path())
);
assert_eq!(build_codex_executable(&app), app.join("ChatGPT.exe"));
assert_eq!(packaged_app_user_model_id(&app), None);
}

#[test]
fn unpacked_package_named_directory_launches_chatgpt_executable_directly() {
let temp = tempfile::tempdir().unwrap();
let app = temp
.path()
.join("OpenAI.Codex_1.2026.133.0_x64__abc")
.join("app");
std::fs::create_dir_all(&app).unwrap();
std::fs::write(app.join("ChatGPT.exe"), "").unwrap();

assert_eq!(packaged_app_user_model_id(&app), None);
assert_eq!(build_packaged_activation(&app, 9229, &[]), None);
assert_eq!(
packaged_app_user_model_id(&app).as_deref(),
Some("OpenAI.Codex_abc!App")
build_codex_command(&app, 9229, &[])[0],
app.join("ChatGPT.exe").to_string_lossy()
);
}

Expand Down
15 changes: 15 additions & 0 deletions crates/codex-plus-core/tests/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ fn find_codex_processes_finds_local_install_with_capitial_c() {
assert_eq!(find_codex_processes_from_snapshot(&processes), vec![42]);
}

#[cfg(windows)]
#[test]
fn find_codex_processes_finds_portable_chatgpt_desktop() {
let processes = [WindowsProcessInfo {
process_id: 46,
parent_process_id: 0,
exe_file: "ChatGPT.exe".to_string(),
executable_path: Some(std::path::PathBuf::from(
r"D:\Apps\OpenAI.Codex_1.2026.133.0_x64__abc\app\ChatGPT.exe",
)),
}];

assert_eq!(find_codex_processes_from_snapshot(&processes), vec![46]);
}

#[cfg(windows)]
#[test]
fn find_codex_processes_ignores_lowercase_local_cli_binary() {
Expand Down