use std::path::Path;
use super::super::super::launch_spec::LaunchSpec;
use super::super::super::managed_launch::{ManagedLaunch, attach_spec};
use super::{attach_id_in_registry, relaunch_spec};
const LIVE_UUID: &str = "74ede5c9-c66d-471e-8dbe-9370d29f6f2e";
const LIVE_SHORT: &str = "74ede5c9";
const STALE_UUID: &str = "11111111-2222-3333-4444-555555555555";
const STOPPED_UUID: &str = "5c30bb17-9d41-4a2e-9f0c-7ab2c1d5e604";
const TEST_CWD: &str = "/tmp/ws";
const TEST_SESSION_ID: &str = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
const REGISTRY_SAMPLE: &str = r#"[
{
"id": "8ec01a01",
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "background",
"startedAt": 1788315744511,
"sessionId": "8ec01a01-3a77-4d7c-b588-456fac3100dd",
"name": "Dog",
"state": "done"
},
{
"id": "a816af2c",
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "background",
"startedAt": 1784481833593,
"sessionId": "a816af2c-818a-4dfa-a2c8-463bdd4eb694",
"name": "resume paused session",
"state": "failed"
},
{
"id": "1905c26f",
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "background",
"startedAt": 1783783191567,
"sessionId": "1905c26f-c66e-4081-8c93-b612090ca104",
"name": "Research web-installer implementation",
"state": "blocked"
},
{
"id": "5c30bb17",
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "background",
"startedAt": 1788500000000,
"sessionId": "5c30bb17-9d41-4a2e-9f0c-7ab2c1d5e604",
"name": "stopped by the operator",
"state": "stopped"
},
{
"pid": 6930,
"id": "74ede5c9",
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "background",
"startedAt": 1788618816891,
"sessionId": "74ede5c9-c66d-471e-8dbe-9370d29f6f2e",
"name": "Mpm dashboard",
"status": "busy",
"state": "working"
},
{
"pid": 9672,
"cwd": "/Users/masa/trusty-mpm-projects/bobmatnyc/trusty-tools",
"kind": "interactive",
"startedAt": 1788618881733,
"sessionId": "e5e08169-b616-4b97-870d-08535eb2f4bc",
"name": "trusty-tools-38",
"status": "busy"
}
]"#;
fn inputs(cwd: &Path) -> ManagedLaunch<'_> {
ManagedLaunch {
cwd,
claude_bin: "/usr/local/bin/claude",
config_dir: None,
session_id: TEST_SESSION_ID,
prompt_file: None,
oauth_token: None,
gh_env: &[],
memory_reachable: true,
mcp_env: &[],
mcp_config: None,
alternate_screen: false,
profile: Default::default(),
}
}
fn argv(spec: &LaunchSpec) -> String {
spec.args.join(" ")
}
fn relaunch_command(
launch: &ManagedLaunch<'_>,
claude_session_id: Option<&str>,
effective_id: Option<&str>,
probe: impl FnOnce() -> Result<String, String>,
) -> String {
argv(&relaunch_spec(
launch,
claude_session_id,
effective_id,
probe,
))
}
#[test]
fn relaunch_attaches_to_a_live_background_session() {
let cwd = Path::new(TEST_CWD);
let cmd = relaunch_command(&inputs(cwd), Some(LIVE_UUID), Some(LIVE_UUID), || {
Ok(REGISTRY_SAMPLE.to_owned())
});
assert!(
cmd.contains(&format!("attach {LIVE_SHORT}")),
"a live background session must be attached: {cmd}"
);
assert!(
!cmd.contains("--resume"),
"a live background session must never be sent --resume: {cmd}"
);
}
#[test]
fn relaunch_resumes_a_session_absent_from_the_registry() {
let cwd = Path::new(TEST_CWD);
let cmd = relaunch_command(&inputs(cwd), Some(STALE_UUID), Some(STALE_UUID), || {
Ok(REGISTRY_SAMPLE.to_owned())
});
assert!(
cmd.contains(&format!("--resume {STALE_UUID}")),
"an unlisted id must resume by id: {cmd}"
);
assert!(
!cmd.contains("attach"),
"an unlisted id must not be attached: {cmd}"
);
}
#[test]
fn relaunch_resumes_when_the_registry_call_fails() {
let cwd = Path::new(TEST_CWD);
let cmd = relaunch_command(&inputs(cwd), Some(LIVE_UUID), Some(LIVE_UUID), || {
Err("`claude agents --json` did not answer within 3s".to_owned())
});
assert!(
cmd.contains(&format!("--resume {LIVE_UUID}")),
"an unreadable registry must fall back to --resume: {cmd}"
);
assert!(
!cmd.contains("attach"),
"an unreadable registry must not attach: {cmd}"
);
}
#[test]
fn relaunch_starts_fresh_without_a_usable_id() {
let cwd = Path::new(TEST_CWD);
let cmd = relaunch_command(&inputs(cwd), None, None, || Ok(REGISTRY_SAMPLE.to_owned()));
assert!(
!cmd.contains("--resume"),
"fresh launch, no --resume: {cmd}"
);
assert!(!cmd.contains("attach"), "fresh launch, no attach: {cmd}");
assert!(
!cmd.contains("--continue"),
"never a bare --continue: {cmd}"
);
}
#[test]
fn relaunch_never_probes_the_registry_without_a_session_id() {
let cwd = Path::new(TEST_CWD);
let probed = std::cell::Cell::new(false);
let cmd = relaunch_command(&inputs(cwd), None, None, || {
probed.set(true);
Ok(REGISTRY_SAMPLE.to_owned())
});
assert!(
!probed.get(),
"the registry must not be read without a claude_session_id: {cmd}"
);
}
#[test]
fn attach_id_found_for_a_live_background_entry() {
assert_eq!(
attach_id_in_registry(REGISTRY_SAMPLE, LIVE_UUID).as_deref(),
Some(LIVE_SHORT)
);
}
#[test]
fn attach_id_absent_for_an_unlisted_session() {
assert_eq!(attach_id_in_registry(REGISTRY_SAMPLE, STALE_UUID), None);
}
#[test]
fn attach_id_absent_for_a_finished_background_entry() {
for finished in [
"8ec01a01-3a77-4d7c-b588-456fac3100dd",
"a816af2c-818a-4dfa-a2c8-463bdd4eb694",
] {
assert_eq!(
attach_id_in_registry(REGISTRY_SAMPLE, finished),
None,
"a done/failed entry must not be attached: {finished}"
);
}
}
#[test]
fn attach_id_absent_for_a_stopped_background_entry() {
assert_eq!(
attach_id_in_registry(REGISTRY_SAMPLE, STOPPED_UUID),
None,
"a stopped entry must resume, never attach"
);
}
#[test]
fn relaunch_resumes_a_stopped_background_session() {
let cwd = Path::new(TEST_CWD);
let cmd = relaunch_command(&inputs(cwd), Some(STOPPED_UUID), Some(STOPPED_UUID), || {
Ok(REGISTRY_SAMPLE.to_owned())
});
assert!(
cmd.contains(&format!("--resume {STOPPED_UUID}")),
"a stopped session must resume by id: {cmd}"
);
assert!(
!cmd.contains("attach"),
"a stopped session must not be attached: {cmd}"
);
}
#[test]
fn attach_id_absent_for_an_interactive_entry() {
assert_eq!(
attach_id_in_registry(REGISTRY_SAMPLE, "e5e08169-b616-4b97-870d-08535eb2f4bc"),
None
);
}
#[test]
fn attach_id_absent_for_unparsable_json() {
for junk in ["", "not json", "{}", "[{\"sessionId\": 7}]"] {
assert_eq!(
attach_id_in_registry(junk, LIVE_UUID),
None,
"unparsable registry must yield no attach id: {junk:?}"
);
}
}
#[test]
fn registry_sample_parses_every_entry_shape() {
let uuids = [
"8ec01a01-3a77-4d7c-b588-456fac3100dd",
"a816af2c-818a-4dfa-a2c8-463bdd4eb694",
"1905c26f-c66e-4081-8c93-b612090ca104",
STOPPED_UUID,
LIVE_UUID,
"e5e08169-b616-4b97-870d-08535eb2f4bc",
];
let attachable: Vec<String> = uuids
.iter()
.filter_map(|u| attach_id_in_registry(REGISTRY_SAMPLE, u))
.collect();
assert_eq!(
attachable,
vec![
"1905c26f".to_owned(),
LIVE_SHORT.to_owned(),
],
"only the non-terminal background entries are attachable"
);
}
#[cfg(unix)]
fn recorded_probe_pid(pidfile: &Path) -> Option<libc::pid_t> {
let recorded = match std::fs::read_to_string(pidfile) {
Ok(text) => text,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return None,
Err(e) => panic!("pid file unreadable for a reason other than absence: {e}"),
};
let trimmed = recorded.trim();
if trimmed.is_empty() {
return None;
}
Some(
trimmed
.parse()
.unwrap_or_else(|e| panic!("a written pid file must hold a pid, got {trimmed:?}: {e}")),
)
}
#[test]
#[cfg(unix)]
fn query_registry_kills_and_reaps_a_wedged_probe() {
use std::os::unix::fs::PermissionsExt as _;
let dir = tempfile::tempdir().expect("tempdir");
let pidfile = dir.path().join("pid");
let script = dir.path().join("wedged-claude");
std::fs::write(
&script,
format!(
"#!/bin/sh\necho $$ > {}\nexec sleep 300\n",
pidfile.display()
),
)
.expect("write stand-in claude");
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).expect("chmod");
let started = std::time::Instant::now();
let err = super::query_registry(script.to_str().expect("utf-8 tempdir path"), None)
.expect_err("a wedged probe must report a timeout, not hang");
assert!(
err.contains("did not answer within"),
"the timeout must be the reported reason: {err}"
);
assert!(
started.elapsed() < std::time::Duration::from_secs(30),
"the probe must return on its own deadline, took {:?}",
started.elapsed()
);
let Some(pid) = recorded_probe_pid(&pidfile) else {
eprintln!("stand-in was killed before recording a pid; liveness unchecked (#7043)");
return;
};
let alive = unsafe { libc::kill(pid, 0) };
assert_eq!(
alive, -1,
"no probe child may outlive the deadline (pid {pid} still exists)"
);
assert_eq!(
std::io::Error::last_os_error().raw_os_error(),
Some(libc::ESRCH),
"the probe child must be gone, not merely unsignalable"
);
}
#[test]
fn attach_command_carries_the_resume_prefixes() {
let cwd = Path::new(TEST_CWD);
let launch = inputs(cwd);
let attach = attach_spec(&launch, LIVE_SHORT);
let resume = super::super::super::managed_launch::resume_spec(&launch, Some(LIVE_UUID));
assert_eq!(attach.cwd, cwd, "rooted at the workspace");
assert_eq!(attach.session_id, TEST_SESSION_ID);
assert_eq!(attach.program, "/usr/local/bin/claude");
assert!(
attach.env_unset.contains(&"ANTHROPIC_API_KEY".to_owned()),
"keeps the env scrub: {:?}",
attach.env_unset
);
assert_eq!(argv(&attach), format!("attach {LIVE_SHORT}"));
assert_eq!(
(attach.env_unset, attach.env_set, attach.cwd, attach.program),
(resume.env_unset, resume.env_set, resume.cwd, resume.program),
"attach and resume must diverge only in argv"
);
}
#[test]
fn attach_command_omits_flags_attach_cannot_take() {
let cwd = Path::new(TEST_CWD);
let prompt = Path::new("/tmp/prompt.md");
let mut with_prompt = inputs(cwd);
with_prompt.prompt_file = Some(prompt);
let cmd = argv(&attach_spec(&with_prompt, LIVE_SHORT));
for flag in [
"--append-system-prompt-file",
"--setting-sources",
"--permission-mode",
"--resume",
] {
assert!(!cmd.contains(flag), "attach must not carry {flag}: {cmd}");
}
}