From 5a0021c6a3ca698fa6ffb9cb5e9c8c3371444f4e Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Thu, 21 May 2026 20:46:40 +0200 Subject: [PATCH] fix: p attach on dead/unknown sessions --- p/src/commands/attach.rs | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/p/src/commands/attach.rs b/p/src/commands/attach.rs index 30e7c0f..58510ff 100644 --- a/p/src/commands/attach.rs +++ b/p/src/commands/attach.rs @@ -11,20 +11,23 @@ pub fn execute(job_id: &str) -> Result<()> { let cfg = config::load()?; let job = db::find(job_id)?.with_context(|| format!("job '{}' not found", job_id))?; - if job.status != JobStatus::Running { - anyhow::bail!( + // Allow attach on Running (normal) and Unknown (worker was temporarily + // unreachable — the job may still be live). + match job.status { + JobStatus::Running | JobStatus::Unknown => {} + _ => anyhow::bail!( "job {} is not running (status: {})\n\ use 'p logs {}' to view its output", job.short_id(), job.status.as_str(), job.short_id(), - ); + ), } let worker = cfg.resolve_worker(Some(&job.worker))?.clone(); let session = format!("p-{}", job.short_id()); - let sid = job.short_id().to_string(); + { let sid = sid.clone(); ctrlc::set_handler(move || { @@ -36,9 +39,30 @@ pub fn execute(job_id: &str) -> Result<()> { .ok(); } - ssh::run_interactive(&worker, &format!("tmux attach -t '{}'", session))?; + let attach_status = ssh::run_interactive(&worker, &format!("tmux attach -t '{}'", session))?; + + // If tmux exited non-zero the session doesn't exist on the worker. + if !attach_status.success() { + let exit_code = ssh::read_job_exitcode(&worker, &job.id); + if exit_code.is_none() { + // No exit code either — the job was lost without completing + // (e.g. worker restarted, tmux server killed). + eprintln!( + "error: tmux session for job {} no longer exists on '{}'.", + sid, worker.name + ); + eprintln!("The job was likely interrupted (worker restart or tmux server exit)."); + eprintln!("Use 'p logs {}' to see whatever output was captured.", sid); + db::save(&Job { + status: JobStatus::Unknown, + ..job + })?; + return Ok(()); + } + // Exit code exists — job finished cleanly but the session was already + // cleaned up. Fall through to normal reconciliation. + } - // Same reconciliation logic as the run command. let exit_code = ssh::read_job_exitcode(&worker, &job.id); let now = Utc::now().timestamp();