huskies: merge 967
This commit is contained in:
@@ -59,6 +59,7 @@ mod tests {
|
||||
child_killers,
|
||||
watcher_tx,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -113,6 +114,7 @@ mod tests {
|
||||
child_killers,
|
||||
watcher_tx,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -175,6 +177,7 @@ mod tests {
|
||||
child_killers,
|
||||
watcher_tx,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let after = chrono::Utc::now();
|
||||
@@ -242,6 +245,7 @@ mod tests {
|
||||
child_killers,
|
||||
watcher_tx,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -373,4 +377,84 @@ mod tests {
|
||||
|
||||
assert!(rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
// ── bug 967: eager session recording survives watchdog kill + task abort ──
|
||||
|
||||
/// AC2 regression: simulates a watchdog kill of an agent that emitted a
|
||||
/// session_id mid-run. The script emits a `"system"` JSON event and then
|
||||
/// sleeps; a concurrent task kills the child after 500 ms (simulating the
|
||||
/// watchdog). The eager-recording path in `run_agent_pty_blocking` must
|
||||
/// have already persisted the session_id before the kill, so
|
||||
/// `lookup_session` returns it (warm) rather than `None` (cold).
|
||||
#[tokio::test]
|
||||
async fn watchdog_kill_session_id_survives_abort() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project_root = tmp.path().to_path_buf();
|
||||
std::fs::create_dir_all(project_root.join(".huskies")).unwrap();
|
||||
|
||||
// Script emits a system event immediately, then sleeps so the process
|
||||
// stays alive long enough for us to kill it (simulating the watchdog).
|
||||
let script = tmp.path().join("emit_then_sleep.sh");
|
||||
std::fs::write(
|
||||
&script,
|
||||
"#!/bin/sh\nprintf '%s\\n' '{\"type\":\"system\",\"session_id\":\"sess-967-watchdog\"}'\nsleep 60\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
|
||||
let (tx, _rx) = broadcast::channel::<AgentEvent>(64);
|
||||
let (watcher_tx, _watcher_rx) = broadcast::channel::<WatcherEvent>(16);
|
||||
let event_log = Arc::new(Mutex::new(Vec::new()));
|
||||
let child_killers: Arc<
|
||||
Mutex<HashMap<String, Box<dyn portable_pty::ChildKiller + Send + Sync>>>,
|
||||
> = Arc::new(Mutex::new(HashMap::new()));
|
||||
let child_killers_for_kill = Arc::clone(&child_killers);
|
||||
|
||||
// Spawn a task to kill the child after a short delay (simulating watchdog).
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
if let Ok(mut killers) = child_killers_for_kill.lock() {
|
||||
for (_, killer) in killers.iter_mut() {
|
||||
let _ = killer.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Run the PTY directly — it returns once the child is killed.
|
||||
let script_arg = script.to_string_lossy().to_string();
|
||||
let _ = run_agent_pty_streaming(
|
||||
"967_story_watchdog",
|
||||
"coder-1",
|
||||
"sh",
|
||||
&[script_arg],
|
||||
"--",
|
||||
"/tmp",
|
||||
&tx,
|
||||
&event_log,
|
||||
None,
|
||||
0, // no inactivity timeout
|
||||
child_killers,
|
||||
watcher_tx,
|
||||
None, // no session to resume
|
||||
Some((project_root.clone(), "sonnet".to_string())),
|
||||
)
|
||||
.await;
|
||||
|
||||
// The session_id must be in the store (eagerly recorded when the
|
||||
// "system" event was seen, before the kill).
|
||||
let recorded = crate::agents::session_store::lookup_session(
|
||||
&project_root,
|
||||
"967_story_watchdog",
|
||||
"coder-1",
|
||||
"sonnet",
|
||||
);
|
||||
assert_eq!(
|
||||
recorded,
|
||||
Some("sess-967-watchdog".to_string()),
|
||||
"session_id must be recorded eagerly before the watchdog kill so \
|
||||
the respawn's lookup_session returns it (warm), not None (cold)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user