mirror of
https://github.com/openai/codex.git
synced 2026-06-02 19:31:59 +00:00
Use deep links for macOS codex app paths (#25485)
## Why `codex app [PATH]` is the documented CLI entry point for opening Codex Desktop on a workspace. Recent desktop builds can focus the app while failing to honor paths passed as macOS document-open arguments via `open -a Codex.app <workspace>`, which broke `codex app .` for users. See #25333; related report: #25166. The desktop app still supports the explicit `codex://threads/new?path=...` route, so the CLI should use that app-owned launch surface instead of depending on folder-open event delivery. ## What Changed - Build a `codex://threads/new?path=<workspace>` URL in the macOS app launcher. - Pass that URL to `open -a <Codex.app>` instead of passing the workspace path as a document argument. - Add coverage that workspace paths needing escaping round-trip through URL query encoding. ## Verification - `just test -p codex-cli codex_new_thread_url_encodes_workspace_path`
This commit is contained in:
@@ -82,10 +82,11 @@ async fn open_codex_app(app_path: &Path, workspace: &Path) -> anyhow::Result<()>
|
||||
"Opening workspace {workspace}...",
|
||||
workspace = workspace.display()
|
||||
);
|
||||
let url = codex_new_thread_url(workspace);
|
||||
let status = Command::new("open")
|
||||
.arg("-a")
|
||||
.arg(app_path)
|
||||
.arg(workspace)
|
||||
.arg(&url)
|
||||
.status()
|
||||
.await
|
||||
.context("failed to invoke `open`")?;
|
||||
@@ -95,12 +96,20 @@ async fn open_codex_app(app_path: &Path, workspace: &Path) -> anyhow::Result<()>
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"`open -a {app_path} {workspace}` exited with {status}",
|
||||
"`open -a {app_path} {url}` exited with {status}",
|
||||
app_path = app_path.display(),
|
||||
workspace = workspace.display()
|
||||
url = url
|
||||
);
|
||||
}
|
||||
|
||||
fn codex_new_thread_url(workspace: &Path) -> String {
|
||||
let workspace = workspace.as_os_str().to_string_lossy();
|
||||
let mut serializer = url::form_urlencoded::Serializer::new(String::new());
|
||||
serializer.append_pair("path", workspace.as_ref());
|
||||
let query = serializer.finish();
|
||||
format!("codex://threads/new?{query}")
|
||||
}
|
||||
|
||||
async fn download_and_install_codex_to_user_applications(dmg_url: &str) -> anyhow::Result<PathBuf> {
|
||||
let temp_dir = Builder::new()
|
||||
.prefix("codex-app-installer-")
|
||||
@@ -293,8 +302,10 @@ fn parse_hdiutil_attach_mount_point(output: &str) -> Option<String> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::codex_new_thread_url;
|
||||
use super::parse_hdiutil_attach_mount_point;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn parses_mount_point_from_tab_separated_hdiutil_output() {
|
||||
@@ -313,4 +324,25 @@ mod tests {
|
||||
Some("/Volumes/Codex Installer")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_new_thread_url_encodes_workspace_path() {
|
||||
let url = url::Url::parse(&codex_new_thread_url(Path::new("/tmp/codex workspace/#1")))
|
||||
.expect("deep link should parse");
|
||||
|
||||
assert_eq!(
|
||||
(
|
||||
url.scheme().to_string(),
|
||||
url.host_str().map(str::to_string),
|
||||
url.path().to_string(),
|
||||
url.query_pairs().into_owned().collect::<Vec<_>>(),
|
||||
),
|
||||
(
|
||||
"codex".to_string(),
|
||||
Some("threads".to_string()),
|
||||
"/new".to_string(),
|
||||
vec![("path".to_string(), "/tmp/codex workspace/#1".to_string())],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user