Skip to content

Commit e517eb9

Browse files
author
Jacob Dwyer
committed
add timeout to WSL credential lookups
wsl.exe -l -q can hang indefinitely on some systems (e.g. WSL installed but not fully configured), which blocks the poll thread forever and prevents any usage data from loading. Adds a run_with_timeout helper (5s deadline) and uses it for both list_wsl_distros and read_wsl_credentials. The refresh paths already had timeouts — these two were missed.
1 parent ec4177f commit e517eb9

File tree

1 file changed

+43
-20
lines changed

1 file changed

+43
-20
lines changed

src/poller.rs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ fn cli_refresh_wsl_token(distro: &str) {
133133
wait_for_refresh(&mut child);
134134
}
135135

136+
/// Spawn a command and wait up to `timeout` for it to finish.
137+
/// Returns None if the process fails to start or exceeds the deadline.
138+
fn run_with_timeout(cmd: &mut Command, timeout: Duration) -> Option<std::process::Output> {
139+
let mut child = cmd.spawn().ok()?;
140+
let start = std::time::Instant::now();
141+
loop {
142+
match child.try_wait() {
143+
Ok(Some(_)) => return child.wait_with_output().ok(),
144+
Ok(None) => {
145+
if start.elapsed() > timeout {
146+
let _ = child.kill();
147+
let _ = child.wait();
148+
return None;
149+
}
150+
std::thread::sleep(Duration::from_millis(100));
151+
}
152+
Err(_) => return None,
153+
}
154+
}
155+
}
156+
136157
fn wait_for_refresh(child: &mut std::process::Child) {
137158
// Wait up to 30 seconds; don't block the poll thread forever.
138159
let start = std::time::Instant::now();
@@ -374,18 +395,19 @@ fn read_credentials_from_source(source: &CredentialSource) -> Option<Credentials
374395
}
375396

376397
fn read_wsl_credentials(distro: &str) -> Option<Credentials> {
377-
let output = Command::new("wsl.exe")
378-
.arg("-d")
379-
.arg(distro)
380-
.arg("--")
381-
.arg("sh")
382-
.arg("-lc")
383-
.arg("cat ~/.claude/.credentials.json")
384-
.creation_flags(CREATE_NO_WINDOW)
385-
.stdout(std::process::Stdio::piped())
386-
.stderr(std::process::Stdio::null())
387-
.output()
388-
.ok()?;
398+
let output = run_with_timeout(
399+
Command::new("wsl.exe")
400+
.arg("-d")
401+
.arg(distro)
402+
.arg("--")
403+
.arg("sh")
404+
.arg("-lc")
405+
.arg("cat ~/.claude/.credentials.json")
406+
.creation_flags(CREATE_NO_WINDOW)
407+
.stdout(std::process::Stdio::piped())
408+
.stderr(std::process::Stdio::null()),
409+
Duration::from_secs(5),
410+
)?;
389411

390412
if !output.status.success() {
391413
return None;
@@ -424,14 +446,15 @@ fn choose_best_credentials(mut candidates: Vec<Credentials>) -> Option<Credentia
424446
}
425447

426448
fn list_wsl_distros() -> Vec<String> {
427-
let output = match Command::new("wsl.exe")
428-
.args(["-l", "-q"])
429-
.creation_flags(CREATE_NO_WINDOW)
430-
.stdout(std::process::Stdio::piped())
431-
.stderr(std::process::Stdio::null())
432-
.output()
433-
{
434-
Ok(output) if output.status.success() => output,
449+
let output = match run_with_timeout(
450+
Command::new("wsl.exe")
451+
.args(["-l", "-q"])
452+
.creation_flags(CREATE_NO_WINDOW)
453+
.stdout(std::process::Stdio::piped())
454+
.stderr(std::process::Stdio::null()),
455+
Duration::from_secs(5),
456+
) {
457+
Some(output) if output.status.success() => output,
435458
_ => return Vec::new(),
436459
};
437460

0 commit comments

Comments
 (0)