Use a Windows-runnable slow command in the progress test

The Windows job hung for the full 60s test timeout waiting for the tool call to finish. The command was `sleep 2`, which cmd cannot run: it is not a builtin, and Git's usr\bin — where the MSYS coreutils live — is kept off the runner's PATH on purpose. `ping -n 3 127.0.0.1` is the cmd equivalent: it ships in System32, waits a second between echoes, and never touches stdin. The command now also gets an explicit cwd rather than falling back to $HOME. Two changes so the next failure explains itself instead of timing out: the elapsed-time assertion prints what the command actually said, and waiting on a session update now fails immediately if the agent asks for permission — these tests run with the gate off, so a request nobody answers would otherwise look exactly like a hang. Co-Authored-By: siGit Code <noreply@sigit.si>

Seto Elkahfi committed Sep 2, 2026 at 23:16 UTC b6f7e3b2ca597419f5de791880527b42c4b185a7
1 file changed +36 -13
tests/acp_permissions.rs
+36 -13
@@ -226,8 +226,16 @@ impl AgentUnderTest {
226 }
227
228 /// The first `session/update` whose payload satisfies `matches`.
229 + ///
230 + /// Only used by tests that run with the permission gate off, so a
231 + /// `session/request_permission` means the run is misconfigured — say that
232 + /// rather than waiting out the timeout on a request nobody will answer.
233 fn wait_for_update(&mut self, what: &str, matches: impl Fn(&Value) -> bool) -> Value {
234 let message = self.wait_for(what, |message| {
235 + assert!(
236 + message["method"] != "session/request_permission",
237 + "unexpected permission request while waiting for {what}: {message}"
238 + );
239 message["method"] == "session/update" && matches(&message["params"]["update"])
240 });
241 message["params"]["update"].clone()
@@ -385,19 +393,33 @@ fn permission_round_trip_cancel_then_allow() {
393
394 #[test]
395 fn a_slow_commands_progress_reaches_the_client_while_it_runs() {
388 - // `SIGIT_PERMISSIONS=allow` skips the gate, so the only thing between the
389 - // two updates below is the command itself.
390 - let endpoint = start_fake_endpoint(vec![
391 - sse_tool_call("call_1", "run_command", r#"{"command":"sleep 2"}"#),
392 - sse_text("done"),
393 - ]);
394 -
396 let scratch = std::env::temp_dir().join(format!("sigit_acp_progress_{}", std::process::id()));
397 let config_dir = scratch.join("config");
398 let cwd = scratch.join("cwd");
399 std::fs::create_dir_all(&config_dir).unwrap();
400 std::fs::create_dir_all(&cwd).unwrap();
401
402 + // The command has to take a couple of seconds through `sh -c` / `cmd /C`.
403 + // `sleep` is neither a cmd builtin nor on a Windows runner's PATH (Git's
404 + // usr\bin, which carries the MSYS coreutils, is deliberately left off it),
405 + // and the Windows job hung on it rather than failing fast. `ping` ships in
406 + // System32, waits a second between echoes, and never reads stdin.
407 + #[cfg(unix)]
408 + let command = "sleep 2";
409 + #[cfg(windows)]
410 + let command = "ping -n 3 127.0.0.1";
411 +
412 + // `SIGIT_PERMISSIONS=allow` skips the gate, so the only thing between the
413 + // two updates below is the command itself.
414 + let endpoint = start_fake_endpoint(vec![
415 + sse_tool_call(
416 + "call_1",
417 + "run_command",
418 + &json!({"command": command, "cwd": cwd}).to_string(),
419 + ),
420 + sse_text("done"),
421 + ]);
422 +
423 let mut agent = spawn_agent_with_permissions(endpoint.port, &config_dir, Some("allow"));
424
425 let id = agent.request(
@@ -429,20 +451,21 @@ fn a_slow_commands_progress_reaches_the_client_while_it_runs() {
451 "a running tool call must be announced as in_progress: {started}"
452 );
453
432 - agent.wait_for_update("the tool call finishing", |update| {
454 + let finished = agent.wait_for_update("the tool call finishing", |update| {
455 update["sessionUpdate"] == "tool_call_update"
456 && update["toolCallId"] == "call_1"
457 && update["status"] == "completed"
458 });
459
438 - // `sleep 2` runs between the two updates. If the turn blocked the
439 - // connection's actors, both would only reach the client once the command
440 - // had already finished, and the editor would never draw a spinner.
460 + // The command runs between the two updates. If the turn blocked the
461 + // connection's actors, both would only reach the client once it had
462 + // already finished, and the editor would never draw a spinner.
463 assert!(
464 in_progress_at.elapsed() >= Duration::from_millis(1_000),
465 "in_progress arrived only {:?} before completed — the client had no \
444 - window to show progress in",
445 - in_progress_at.elapsed()
466 + window to show progress in. `{command}` said: {}",
467 + in_progress_at.elapsed(),
468 + finished["rawOutput"]
469 );
470
471 let response = agent.wait_for_response(prompt_id);