Fix #5236: cap the wait for open -a at two seconds

A stalled `open -a` no longer holds the window: Edit Original waits at most
two seconds for its exit status (enough to report an app it can't open),
then leaves the launch running and reaps it on a thread of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ruchirguitar
2026-11-03 19:57:04 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 7c5670b797
commit 79c8d1a7e0
+30 -9
View File
@@ -357,18 +357,39 @@ fn open_file_with(app: &str, path: &str) -> Result<(), String> {
c.arg(path);
c
};
// macOS: `open` returns once Launch Services has the request, so waiting for it is brief and
// reports an app it can't open (a folder named `.app` that isn't a bundle). Elsewhere the
// editor itself runs: waiting would last until it closes.
// macOS: `open` returns once Launch Services has the request, so a short wait reports an app
// it can't open (a folder named `.app` that isn't a bundle). The wait is capped so a stalled
// `open` never holds the window: past it, the launch is left to run. Elsewhere the editor
// itself runs: waiting would last until it closes.
#[cfg(target_os = "macos")]
{
let out = c.output().map_err(|e| format!("can't open {path} in {}: {e}", app.display()))?;
if out.status.success() {
return Ok(());
const WAIT: std::time::Duration = std::time::Duration::from_secs(2);
let fail = |why: String| format!("can't open {path} in {}: {why}", app.display());
let mut child = c.stderr(std::process::Stdio::piped()).spawn().map_err(|e| fail(e.to_string()))?;
let started = std::time::Instant::now();
loop {
match child.try_wait() {
Ok(Some(status)) if status.success() => return Ok(()),
Ok(Some(status)) => {
let mut why = String::new();
if let Some(mut err) = child.stderr.take() {
// Best effort: without its message the exit status says why.
let _ = std::io::Read::read_to_string(&mut err, &mut why);
}
let why = why.trim().to_string();
return Err(fail(if why.is_empty() { status.to_string() } else { why }));
}
Ok(None) if started.elapsed() < WAIT => std::thread::sleep(std::time::Duration::from_millis(20)),
Ok(None) => {
// Reaped off the UI thread once it ends.
std::thread::spawn(move || {
let _ = child.wait();
});
return Ok(());
}
Err(e) => return Err(fail(e.to_string())),
}
}
let why = String::from_utf8_lossy(&out.stderr).trim().to_string();
let why = if why.is_empty() { out.status.to_string() } else { why };
Err(format!("can't open {path} in {}: {why}", app.display()))
}
#[cfg(not(target_os = "macos"))]
c.spawn().map(|_| ()).map_err(|e| format!("can't open {path} in {}: {e}", app.display()))