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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ on:

jobs:
check:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
Expand Down
29 changes: 28 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,12 @@ impl App {
// Check if we have a pending confirmation for this exact session
if let Some((idx, ts)) = self.kill_confirm.take() {
if idx == self.selected && ts.elapsed().as_secs() < 2 {
// Confirmed — verify PID still runs a killable agent before killing
// Confirmed — verify PID still runs a killable agent before killing.
// Note (review point 2): this guard only verifies the PID is a known
// agent process, not that it owns the displayed session. Real
// ownership is the root fix; this is the platform stopgap.
let pid = session.pid;
#[cfg(not(target_os = "windows"))]
let verified = std::process::Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "command="])
.output()
Expand All @@ -730,13 +734,23 @@ impl App {
is_killable_agent_command(&cmd)
})
.unwrap_or(false);
#[cfg(target_os = "windows")]
let verified = verify_killable_agent_windows(pid);
if !verified {
self.set_status(format!("PID {} is no longer a known agent process", pid));
return;
}
#[cfg(not(target_os = "windows"))]
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output();
// Windows has no `kill`; use taskkill /F /PID. taskkill is the
// standard Windows process-termination tool (review point 2:
// the previous `kill -9` silently failed on Windows).
#[cfg(target_os = "windows")]
let _ = std::process::Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.output();
self.tick();
return;
}
Expand Down Expand Up @@ -1009,6 +1023,19 @@ fn is_killable_agent_command(cmd: &str) -> bool {
&& !(crate::collector::process::cmd_has_binary(cmd, "codex") && cmd.contains(" app-server"))
}

/// Windows equivalent of the `ps -p <pid> -o command=` guard: look up the
/// PID's command line via sysinfo (the dependency already used by
/// `process::get_process_info` on Windows) and run the same killable-agent
/// check. Stops the kill path from silently failing on Windows where `ps`
/// does not exist (review point 2 stopgap).
#[cfg(target_os = "windows")]
fn verify_killable_agent_windows(pid: u32) -> bool {
let info = crate::collector::process::get_process_info();
info.get(&pid)
.map(|p| is_killable_agent_command(&p.command))
.unwrap_or(false)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading