jobrun: a refused, cancelled or timed-out administrator prompt fails the job (exit 251)

The elevated launcher exited 0 after Windows cancelled an unanswered UAC prompt at 122 s (PC 1 driver job, 5 October 2026), so the job read as done. Start-Process now runs under -ErrorAction Stop in a try/catch, a missing process is exit 251, and the summary says what to do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-05 18:05:08 +00:00
parent 7d09857f91
commit 574eacc265

View file

@ -1119,7 +1119,10 @@ fn run_script(shared: &Arc<Shared>, job: &Job, sink: &Sink, jobs_dir: &Path, dat
std::fs::write(&wrapper, [b"\xEF\xBB\xBF".as_slice(), w.as_bytes()].concat()).map_err(|e| e.to_string())?;
let _ = std::fs::remove_file(&out_file);
let inner = format!("-NoProfile -ExecutionPolicy Bypass -File \"{}\"", wrapper.display());
let ps = format!("$p = Start-Process -FilePath powershell.exe -ArgumentList '{}' -Verb RunAs -Wait -WindowStyle Hidden -PassThru; exit $p.ExitCode", inner.replace('\'', "''"));
// A refused or unanswered UAC prompt makes Start-Process throw (`$p` stays null) and `exit $p.ExitCode`
// would exit 0: the 5 October 2026 driver job on PC 1 was reported "done" after Windows cancelled its
// prompt at 122 s. The launch failure is exit 251 and says so on stderr.
let ps = format!("try {{ $p = Start-Process -FilePath powershell.exe -ArgumentList '{}' -Verb RunAs -Wait -WindowStyle Hidden -PassThru -ErrorAction Stop }} catch {{ Write-Error ('elevated launch failed (UAC refused, cancelled or timed out): ' + $_.Exception.Message); exit 251 }}; if ($null -eq $p) {{ Write-Error 'elevated launch failed: no process'; exit 251 }}; exit $p.ExitCode", inner.replace('\'', "''"));
cmd = Command::new(crate::platform::tool("powershell"));
cmd.args(["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", &ps]);
} else if shell == "powershell" {
@ -1145,6 +1148,7 @@ fn run_script(shared: &Arc<Shared>, job: &Job, sink: &Sink, jobs_dir: &Path, dat
fn finish_ran(ran: Ran, what: &str) -> Result<Done, String> {
match ran.code {
Some(0) => Ok(Done { status: "done".into(), exit: 0, summary: format!("{what} finished, exit 0"), extra: json!({}) }),
Some(251) => Ok(Done { status: "failed".into(), exit: 251, summary: format!("{what} did not start: the administrator prompt was refused, cancelled or timed out (click Yes within 2 minutes)"), extra: json!({}) }),
Some(c) => Ok(Done { status: "failed".into(), exit: c as i64, summary: format!("{what} exited with code {c}"), extra: json!({}) }),
None if ran.timed_out => Ok(Done { status: "timeout".into(), exit: -1, summary: format!("{what} hit the time cap and was ended"), extra: json!({}) }),
None => Err(format!("{what} was ended")),
@ -1259,6 +1263,20 @@ fn collect_done(uploaded: u32, failed: u32, names: Vec<String>, ran: Option<Ran>
mod tests {
use super::*;
#[test]
fn a_refused_administrator_prompt_is_a_failure_not_done() {
// 5 October 2026: the elevated launcher exited 0 after Windows cancelled an unanswered UAC prompt
let d = finish_ran(Ran { code: Some(251), timed_out: false }, "script").unwrap();
assert_eq!((d.status.as_str(), d.exit), ("failed", 251));
assert!(d.summary.contains("administrator prompt"), "{}", d.summary);
let d = finish_ran(Ran { code: Some(0), timed_out: false }, "script").unwrap();
assert_eq!(d.status, "done");
// the launcher string itself: a thrown Start-Process must not fall through to `exit $p.ExitCode`
let src = include_str!("jobrun.rs");
assert!(src.contains("-Verb RunAs -Wait -WindowStyle Hidden -PassThru -ErrorAction Stop }} catch {{"));
assert!(src.contains("if ($null -eq $p) {{ Write-Error 'elevated launch failed: no process'; exit 251 }}"));
}
#[test]
fn collect_outcome_follows_the_command_exit() {
let d = collect_done(0, 0, vec![], None);