feat(maestro): prune sidecars whose cwd no longer exists
recover()'s dormant-registration pass (#162) now deletes a sidecar instead of registering it dormant when the directory it points at is gone. Nothing can be resumed there, so the honest move is to remove the record rather than keep an unactionable one around (#163). Sidecars whose cwd still exists are unaffected, regardless of age.
This commit is contained in:
@@ -756,6 +756,11 @@ impl SessionManager {
|
||||
///
|
||||
/// Activity is not carried over: whatever the session was doing, it is not
|
||||
/// doing it now. The rest of the sidecar is left as written.
|
||||
///
|
||||
/// #163: a sidecar whose `cwd` no longer exists is pruned instead —
|
||||
/// nothing can be resumed in a directory that is gone, so the honest move
|
||||
/// is to delete the sidecar rather than register a record no operator can
|
||||
/// act on. A sidecar whose `cwd` still exists is kept regardless of age.
|
||||
async fn register_dormant(&self) {
|
||||
let stored = match self.inner.sidecar.load_all() {
|
||||
Ok(stored) => stored,
|
||||
@@ -765,6 +770,7 @@ impl SessionManager {
|
||||
}
|
||||
};
|
||||
let mut registered = Vec::new();
|
||||
let mut pruned = 0usize;
|
||||
{
|
||||
let mut sessions = self.inner.sessions.lock().await;
|
||||
for mut session in stored {
|
||||
@@ -772,6 +778,13 @@ impl SessionManager {
|
||||
// Adopted live by the first pass; its sidecar is on disk too.
|
||||
continue;
|
||||
}
|
||||
if !session.cwd.exists() {
|
||||
if let Err(e) = self.inner.sidecar.delete(session.id) {
|
||||
tracing::warn!(error = ?e, id = %session.id, "recover: failed to prune sidecar with missing cwd");
|
||||
}
|
||||
pruned += 1;
|
||||
continue;
|
||||
}
|
||||
let id = session.id;
|
||||
session.status = SessionStatus::Dormant;
|
||||
session.foreground = None;
|
||||
@@ -805,6 +818,9 @@ impl SessionManager {
|
||||
"registered sessions with no live tmux as dormant"
|
||||
);
|
||||
}
|
||||
if pruned > 0 {
|
||||
tracing::info!(pruned, "pruned sidecars whose cwd no longer exists");
|
||||
}
|
||||
for session in registered {
|
||||
let _ = self
|
||||
.inner
|
||||
|
||||
@@ -234,6 +234,69 @@ async fn recover_does_not_mark_adopted_sessions_dormant() {
|
||||
assert_eq!(snap[1].status, SessionStatus::Dormant);
|
||||
}
|
||||
|
||||
/// #163: a sidecar whose `cwd` no longer exists is pruned instead of
|
||||
/// registered dormant — the worktree it pointed at is gone, so there is
|
||||
/// nothing left to resume.
|
||||
#[tokio::test]
|
||||
async fn recover_deletes_sidecar_when_cwd_gone() {
|
||||
let fx = DaemonFixture::new();
|
||||
let cwd = TempDir::new().unwrap();
|
||||
let cwd_path = cwd.path().to_path_buf();
|
||||
|
||||
let s = fx
|
||||
.manager
|
||||
.create(CreateSessionRequest {
|
||||
cwd: cwd_path.clone(),
|
||||
command: "sleep 30".into(),
|
||||
env: HashMap::new(),
|
||||
metadata: HashMap::new(),
|
||||
worktree: None,
|
||||
})
|
||||
.await
|
||||
.expect("create");
|
||||
|
||||
fx.tmux.tmux.kill_server().expect("kill tmux server");
|
||||
drop(cwd);
|
||||
std::fs::remove_dir_all(&cwd_path).ok();
|
||||
assert!(!cwd_path.exists());
|
||||
|
||||
let revived = revive(&fx);
|
||||
let adopted = revived.recover().await.expect("recover");
|
||||
assert_eq!(adopted, 0);
|
||||
|
||||
assert!(!revived.has(s.id).await);
|
||||
assert!(!fx.sidecar_dir().join(format!("{}.json", s.id)).exists());
|
||||
}
|
||||
|
||||
/// #163: a sidecar whose `cwd` still exists is left alone regardless of age
|
||||
/// — it is registered dormant like any other orphan.
|
||||
#[tokio::test]
|
||||
async fn recover_keeps_dormant_sidecar_when_cwd_exists() {
|
||||
let fx = DaemonFixture::new();
|
||||
let cwd = TempDir::new().unwrap();
|
||||
|
||||
let s = fx
|
||||
.manager
|
||||
.create(CreateSessionRequest {
|
||||
cwd: cwd.path().to_path_buf(),
|
||||
command: "sleep 30".into(),
|
||||
env: HashMap::new(),
|
||||
metadata: HashMap::new(),
|
||||
worktree: None,
|
||||
})
|
||||
.await
|
||||
.expect("create");
|
||||
fx.tmux.tmux.kill_server().expect("kill tmux server");
|
||||
|
||||
let revived = revive(&fx);
|
||||
revived.recover().await.expect("recover");
|
||||
|
||||
assert!(revived.has(s.id).await);
|
||||
assert!(fx.sidecar_dir().join(format!("{}.json", s.id)).exists());
|
||||
let snap = revived.snapshot().await;
|
||||
assert_eq!(snap[0].status, SessionStatus::Dormant);
|
||||
}
|
||||
|
||||
/// #162: dismissing a dormant session is the one thing that can be done to it
|
||||
/// — no tmux call to fail, and the sidecar goes with it so the next recovery
|
||||
/// does not resurrect it.
|
||||
|
||||
Reference in New Issue
Block a user