use std::path::{Path, PathBuf};
use std::sync::Mutex;
use super::super::launch_spec::LaunchSpec;
use super::*;
use crate::session_manager::{ManagedError, ManagedTmuxDriver};
fn long_path(prefix: &str, segments: usize) -> PathBuf {
let mut p = PathBuf::from(prefix);
for i in 0..segments {
p.push(format!("an-uncomfortably-long-path-segment-{i:03}"));
}
p
}
struct Worst {
cwd: PathBuf,
config_dir: PathBuf,
mcp_config: PathBuf,
prompt: PathBuf,
mcp_env: Vec<(String, String)>,
gh_env: Vec<(String, String)>,
oauth_token: String,
}
impl Worst {
fn new() -> Self {
Self {
cwd: long_path("/Users/an-operator-with-a-long-name/trusty-mpm-projects", 5),
config_dir: long_path("/Users/an-operator-with-a-long-name/.trusty-tools", 3),
mcp_config: long_path(
"/Users/an-operator-with-a-long-name/.trusty-tools/trusty-mpm/session-mcp",
3,
)
.join("0123456789abcdef0123456789abcdef.json"),
prompt: long_path("/var/folders/qv/8k3p1x9n5cl7g2vy_0000gn/T", 4)
.join("trusty-mpm-prompt.txt"),
mcp_env: vec![
(
"TRUSTY_MEMORY_PALACE".to_owned(),
long_path("/palaces", 3).display().to_string(),
),
(
"TRUSTY_INDEX".to_owned(),
long_path("/indexes", 3).display().to_string(),
),
],
gh_env: vec![
("GH_TOKEN".to_owned(), "gho_".to_owned() + &"x".repeat(60)),
(
"GH_USER".to_owned(),
"an-operator-with-a-long-name".to_owned(),
),
(
"GH_CONFIG_DIR".to_owned(),
long_path("/gh-configs", 3).display().to_string(),
),
],
oauth_token: "sk-ant-oat01-".to_owned() + &"y".repeat(96),
}
}
fn launch(&self) -> ManagedLaunch<'_> {
ManagedLaunch {
cwd: &self.cwd,
claude_bin: "/Users/an-operator-with-a-long-name/.local/bin/claude",
config_dir: Some(&self.config_dir),
session_id: "dd0e2fb8-1111-2222-3333-444455556666",
prompt_file: Some(&self.prompt),
oauth_token: Some(&self.oauth_token),
gh_env: &self.gh_env,
mcp_env: &self.mcp_env,
mcp_config: Some(&self.mcp_config),
memory_reachable: true,
alternate_screen: true,
}
}
}
fn bare_launch<'a>(cwd: &'a Path, gh_env: &'a [(String, String)]) -> ManagedLaunch<'a> {
ManagedLaunch {
cwd,
claude_bin: "/abs/claude",
config_dir: None,
session_id: "11111111-2222-3333-4444-555555555555",
prompt_file: None,
oauth_token: None,
gh_env,
mcp_env: &[],
mcp_config: None,
memory_reachable: false,
alternate_screen: false,
}
}
fn value_of<'a>(set: &'a [(String, String)], name: &str) -> Option<&'a str> {
set.iter().find(|(k, _)| k == name).map(|(_, v)| v.as_str())
}
#[test]
fn env_unset_leads_with_the_api_key() {
assert_eq!(managed_env_unset(&[])[0], "ANTHROPIC_API_KEY");
}
#[test]
fn env_unset_carries_every_inherited_session_marker() {
let unset = managed_env_unset(&[]);
for marker in crate::core::claude_env_scrub::INHERITED_SESSION_MARKERS {
assert!(
unset.iter().any(|n| n == marker),
"missing {marker}: {unset:?}"
);
}
}
#[test]
fn env_unset_clears_an_inherited_gh_token() {
let gh_env = vec![("GH_CONFIG_DIR".to_owned(), "/gh".to_owned())];
let unset = managed_env_unset(&gh_env);
assert!(
unset.iter().any(|n| n == "GH_TOKEN"),
"a pinned gh identity must clear an inherited token: {unset:?}"
);
}
#[test]
fn env_unset_clears_nothing_without_a_gh_identity_binding() {
let unset = managed_env_unset(&[]);
assert!(
!unset.iter().any(|n| n == "GH_TOKEN"),
"no identity binding must clear nothing: {unset:?}"
);
}
#[test]
fn env_set_enables_todo_tools_unconditionally() {
for reachable in [true, false] {
let set = managed_env_set(None, None, &[], reachable, &[]);
assert_eq!(value_of(&set, "CLAUDE_CODE_ENABLE_TODO_TOOLS"), Some("1"));
}
}
#[test]
fn env_set_disables_auto_memory_when_trusty_memory_answers() {
let set = managed_env_set(None, None, &[], true, &[]);
assert_eq!(value_of(&set, "CLAUDE_CODE_DISABLE_AUTO_MEMORY"), Some("1"));
}
#[test]
fn env_set_keeps_auto_memory_when_trusty_memory_is_unreachable() {
let set = managed_env_set(None, None, &[], false, &[]);
assert_eq!(value_of(&set, "CLAUDE_CODE_DISABLE_AUTO_MEMORY"), None);
}
#[test]
fn env_set_relocates_the_config_dir() {
let dir = PathBuf::from("/managed/claude-config");
let set = managed_env_set(Some(&dir), None, &[], true, &[]);
assert_eq!(
value_of(&set, "CLAUDE_CONFIG_DIR"),
Some("/managed/claude-config")
);
}
#[test]
fn env_set_carries_a_non_empty_mcp_env() {
let mcp = vec![("TRUSTY_INDEX".to_owned(), "idx".to_owned())];
let set = managed_env_set(None, None, &mcp, true, &[]);
assert_eq!(value_of(&set, "TRUSTY_INDEX"), Some("idx"));
}
#[test]
fn env_set_carries_the_oauth_token_when_available() {
let set = managed_env_set(None, Some("tok"), &[], true, &[]);
assert_eq!(
value_of(&set, crate::core::oauth_token::OAUTH_TOKEN_ENV_VAR),
Some("tok")
);
}
#[test]
fn env_set_omits_the_oauth_token_when_absent() {
let set = managed_env_set(None, None, &[], true, &[]);
assert_eq!(
value_of(&set, crate::core::oauth_token::OAUTH_TOKEN_ENV_VAR),
None
);
}
#[test]
fn env_set_carries_the_gh_identity() {
let gh = vec![("GH_TOKEN".to_owned(), "gho_x".to_owned())];
let set = managed_env_set(None, None, &[], true, &gh);
assert_eq!(value_of(&set, "GH_TOKEN"), Some("gho_x"));
}
#[test]
fn spawn_argv_matches_the_shell_line_it_replaces() {
let w = Worst::new();
let launch = w.launch();
let spec = spawn_spec(&launch);
let mut expected = vec![
"--append-system-prompt-file".to_owned(),
w.prompt.display().to_string(),
];
expected.extend(
crate::core::model_inject::setting_sources_flag(Some(&w.config_dir))
.split_whitespace()
.map(str::to_owned),
);
expected.extend(crate::core::session_mcp_scope::mcp_config_argv(Some(
&w.mcp_config,
)));
expected.extend(
crate::core::model_inject::PERMISSION_MODE_FLAG
.split_whitespace()
.map(str::to_owned),
);
assert_eq!(spec.args, expected);
}
#[test]
fn spawn_argv_quotes_nothing() {
let w = Worst::new();
let spec = spawn_spec(&w.launch());
assert!(
spec.args.iter().all(|a| !a.contains('\'')),
"argv tokens must be bare, not shell-quoted: {:?}",
spec.args
);
}
#[test]
fn spawn_spec_roots_the_child_at_the_workspace() {
let w = Worst::new();
let spec = spawn_spec(&w.launch());
assert_eq!(spec.cwd, w.cwd);
assert_eq!(
spec.program,
"/Users/an-operator-with-a-long-name/.local/bin/claude"
);
}
#[test]
fn resume_spec_appends_the_resume_flag() {
let cwd = PathBuf::from("/w");
let launch = bare_launch(&cwd, &[]);
let spec = resume_spec(&launch, Some("conv-123"));
let tail = &spec.args[spec.args.len() - 2..];
assert_eq!(tail, ["--resume".to_owned(), "conv-123".to_owned()]);
}
#[test]
fn resume_spec_without_an_id_is_a_plain_spawn() {
let cwd = PathBuf::from("/w");
let launch = bare_launch(&cwd, &[]);
assert_eq!(resume_spec(&launch, None).args, spawn_spec(&launch).args);
assert!(
!resume_spec(&launch, None)
.args
.iter()
.any(|a| a == "--continue")
);
}
#[test]
fn attach_spec_omits_flags_attach_cannot_take() {
let w = Worst::new();
let spec = attach_spec(&w.launch(), "short1");
assert_eq!(spec.args, ["attach".to_owned(), "short1".to_owned()]);
}
#[test]
fn attach_and_resume_share_an_identical_environment() {
let w = Worst::new();
let launch = w.launch();
let attach = attach_spec(&launch, "short1");
let resume = resume_spec(&launch, Some("conv"));
assert_eq!(attach.env_unset, resume.env_unset);
assert_eq!(attach.env_set, resume.env_set);
assert_eq!(attach.cwd, resume.cwd);
assert_eq!(attach.program, resume.program);
assert_eq!(attach.session_id, resume.session_id);
}
#[test]
fn every_launch_path_carries_the_configured_fullscreen_renderer() {
use crate::core::alt_screen::{ALT_SCREEN_ENABLED, ALT_SCREEN_ENV_VAR};
let w = Worst::new();
let launch = w.launch();
for spec in [
spawn_spec(&launch),
resume_spec(&launch, Some("conv")),
attach_spec(&launch, "short1"),
] {
assert_eq!(
value_of(&spec.env_set, ALT_SCREEN_ENV_VAR),
Some(ALT_SCREEN_ENABLED),
"args {:?}",
spec.args
);
let cmd = spec.to_command();
let carried = cmd
.get_envs()
.find(|(k, _)| *k == ALT_SCREEN_ENV_VAR)
.and_then(|(_, v)| v.map(|v| v.to_string_lossy().into_owned()));
assert_eq!(carried.as_deref(), Some(ALT_SCREEN_ENABLED));
}
}
#[test]
fn every_launch_path_carries_the_classic_renderer_when_alternate_screen_is_off() {
let cwd = PathBuf::from("/work");
let launch = bare_launch(&cwd, &[]);
for spec in [spawn_spec(&launch), attach_spec(&launch, "short1")] {
assert_eq!(
value_of(&spec.env_set, crate::core::alt_screen::ALT_SCREEN_ENV_VAR),
Some(crate::core::alt_screen::ALT_SCREEN_DEFAULT)
);
}
}
#[test]
fn pane_line_exports_the_managed_session_id() {
let line = pane_line("sess-id", "/usr/local/bin/tm", Path::new("/specs/a.json"));
assert!(
line.starts_with("export TM_MANAGED_SESSION_ID='sess-id'; "),
"{line}"
);
}
#[test]
fn pane_line_routes_through_the_disclaim_shim() {
let line = pane_line("s", "/usr/local/bin/tm", Path::new("/specs/a.json"));
assert!(
line.contains(
"'/usr/local/bin/tm' internal-spawn-disclaimed --launch-spec '/specs/a.json'"
),
"{line}"
);
}
#[test]
fn pane_line_is_short_for_the_worst_case_launch() {
let w = Worst::new();
let spec = spawn_spec(&w.launch());
let spec_json = serde_json::to_string(&spec).expect("spec serialises");
assert!(
spec_json.len() > 1_024,
"the worst case must genuinely be large: {} bytes",
spec_json.len()
);
let wrapper = long_path("/Users/an-operator-with-a-long-name/.cargo/bin", 2)
.join("tm")
.display()
.to_string();
let spec_path = LaunchSpec::root()
.unwrap_or_else(|| {
PathBuf::from(
"/Users/an-operator-with-a-long-name/.trusty-tools/trusty-mpm/launch-specs",
)
})
.join(format!("{}.json", uuid::Uuid::new_v4()));
let line = pane_line(&spec.session_id, &wrapper, &spec_path);
assert!(
line.len() < 512,
"the typed line must stay under 512 bytes; got {} for {line}",
line.len()
);
assert!(
line.len() < crate::core::tmux::MAX_PANE_COMMAND_BYTES,
"the typed line must pass the pane-command guard: {} bytes",
line.len()
);
let bare = pane_line(&spec.session_id, &wrapper, &spec_path);
assert_eq!(
line.len(),
bare.len(),
"the typed line's length must be independent of the launch's contents"
);
}
#[test]
fn abort_notice_is_short_enough_to_always_type() {
let notice = abort_notice("dd0e2fb8-1111-2222-3333-444455556666");
assert!(
notice.len() < 256,
"the abort notice must always fit: {} bytes",
notice.len()
);
assert!(notice.contains("aborted"), "{notice}");
}
struct Recorder {
sends: Mutex<Vec<String>>,
fail_first: usize,
interrupts: Mutex<Vec<(String, Option<String>)>>,
pane_text: Mutex<String>,
events: Mutex<Vec<String>>,
stays_wedged: bool,
}
impl Recorder {
fn new(fail_first: usize) -> Self {
Self {
sends: Mutex::new(Vec::new()),
fail_first,
interrupts: Mutex::new(Vec::new()),
pane_text: Mutex::new("~ %".to_owned()),
events: Mutex::new(Vec::new()),
stays_wedged: false,
}
}
fn wedged() -> Self {
let r = Self::new(0);
*r.pane_text.lock().expect("recorder mutex") = "quote>".to_owned();
r
}
fn wedged_forever() -> Self {
let mut r = Self::wedged();
r.stays_wedged = true;
r
}
fn events(&self) -> Vec<String> {
self.events.lock().expect("recorder mutex").clone()
}
fn answer_probe(&self, text: &str) -> bool {
let Some(out) = super::super::pane_handshake::probe_reply(text) else {
return false;
};
let mut pane = self.pane_text.lock().expect("recorder mutex");
if pane.trim_end().ends_with("quote>") {
return true;
}
pane.push('\n');
pane.push_str(&out);
true
}
fn lines(&self) -> Vec<String> {
self.sends.lock().expect("recorder mutex").clone()
}
fn resets(&self) -> Vec<(String, Option<String>)> {
self.interrupts.lock().expect("recorder mutex").clone()
}
}
impl ManagedTmuxDriver for Recorder {
fn create_session(&self, _name: &str, _workdir: &str) -> Result<(), ManagedError> {
Ok(())
}
fn kill_session(&self, _name: &str) -> Result<(), ManagedError> {
Ok(())
}
fn send_line(&self, _name: &str, text: &str) -> Result<(), ManagedError> {
if self.answer_probe(text) {
return Ok(());
}
self.events
.lock()
.expect("recorder mutex")
.push(format!("line:{text}"));
let mut log = self.sends.lock().expect("recorder mutex");
log.push(text.to_owned());
if log.len() <= self.fail_first {
return Err(ManagedError::TmuxUnavailable("pane is gone".into()));
}
Ok(())
}
fn send_line_to_pane(&self, _name: &str, _pane: &str, text: &str) -> Result<(), ManagedError> {
if self.answer_probe(text) {
return Ok(());
}
self.events
.lock()
.expect("recorder mutex")
.push(format!("line:{text}"));
let mut log = self.sends.lock().expect("recorder mutex");
log.push(text.to_owned());
if log.len() <= self.fail_first {
return Err(ManagedError::TmuxUnavailable("pane is gone".into()));
}
Ok(())
}
fn send_interrupt(&self, name: &str) -> Result<(), ManagedError> {
self.events
.lock()
.expect("recorder mutex")
.push("reset:session".to_owned());
if !self.stays_wedged {
*self.pane_text.lock().expect("recorder mutex") = "~ %".to_owned();
}
self.interrupts
.lock()
.expect("recorder mutex")
.push((name.to_owned(), None));
Ok(())
}
fn send_interrupt_to_pane(&self, name: &str, pane: &str) -> Result<(), ManagedError> {
self.events
.lock()
.expect("recorder mutex")
.push(format!("reset:{pane}"));
if !self.stays_wedged {
*self.pane_text.lock().expect("recorder mutex") = "~ %".to_owned();
}
self.interrupts
.lock()
.expect("recorder mutex")
.push((name.to_owned(), Some(pane.to_owned())));
Ok(())
}
fn capture(&self, _name: &str, _lines: usize) -> Result<String, ManagedError> {
Ok(self.pane_text.lock().expect("recorder mutex").clone())
}
fn capture_pane(&self, name: &str, _pane: &str, lines: usize) -> Result<String, ManagedError> {
self.capture(name, lines)
}
fn list_sessions(&self) -> Result<Vec<String>, ManagedError> {
Ok(vec!["tm-sess".to_owned()])
}
}
#[test]
fn deliver_types_a_short_line_and_leaves_a_readable_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let w = Worst::new();
let spec = spawn_spec(&w.launch());
let tmux = Recorder::new(0);
deliver_in(&tmux, "tm-sess", None, &spec, dir.path()).expect("deliver");
let lines = tmux.lines();
assert_eq!(lines.len(), 1, "exactly one line is typed: {lines:?}");
assert!(
lines[0].len() < 512,
"typed {} bytes: {}",
lines[0].len(),
lines[0]
);
let written: Vec<_> = std::fs::read_dir(dir.path())
.expect("readdir")
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|e| e == "json"))
.collect();
assert_eq!(written.len(), 1, "one spec per launch: {written:?}");
assert_eq!(LaunchSpec::verify(&written[0]).expect("verify"), spec);
}
#[test]
fn deliver_resets_the_pane_before_typing() {
let dir = tempfile::tempdir().expect("tempdir");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
let tmux = Recorder::wedged();
deliver_in(&tmux, "tm-sess", None, &spec, dir.path()).expect("deliver");
assert_eq!(
tmux.resets(),
vec![("tm-sess".to_owned(), None)],
"the wedged pane must be interrupted exactly once, session-scoped with no pane id"
);
let events = tmux.events();
let reset_at = events
.iter()
.position(|e| e.starts_with("reset:"))
.expect("the pane was reset");
let line_at = events
.iter()
.position(|e| e.starts_with("line:"))
.expect("the launch line was typed");
assert!(
reset_at < line_at,
"the reset must precede the keystrokes, or the open construct swallows them: {events:?}"
);
assert_eq!(tmux.lines().len(), 1, "and then the launch line is typed");
}
#[test]
fn deliver_resets_the_named_pane_when_one_is_known() {
let dir = tempfile::tempdir().expect("tempdir");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
let tmux = Recorder::wedged();
deliver_in(&tmux, "tm-sess", Some("%9"), &spec, dir.path()).expect("deliver");
assert_eq!(
tmux.resets(),
vec![("tm-sess".to_owned(), Some("%9".to_owned()))]
);
let events = tmux.events();
assert!(
events.iter().any(|e| e == "reset:%9"),
"the interrupt must name the pane, never the session: {events:?}"
);
let reset_at = events.iter().position(|e| e == "reset:%9").expect("reset");
let line_at = events
.iter()
.position(|e| e.starts_with("line:"))
.expect("launch line");
assert!(reset_at < line_at, "{events:?}");
}
#[test]
fn deliver_clears_a_stale_started_sentinel() {
let dir = tempfile::tempdir().expect("tempdir");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
std::fs::create_dir_all(dir.path()).expect("mkdir");
let stale_launch_id = "0000staleaaaabbbbccccddddeeee1111";
let stale = LaunchSpec::started_marker_in(dir.path(), stale_launch_id);
std::fs::write(&stale, b"").expect("plant a stale sentinel");
deliver_in(&Recorder::new(0), "tm-sess", None, &spec, dir.path()).expect("deliver");
let current = LaunchSpec::read_launch_pointer_in(dir.path(), &spec.session_id)
.expect("delivery publishes the launch this checker must wait on");
assert_eq!(current, spec.launch_id);
assert_ne!(
current, stale_launch_id,
"a launch must never inherit an earlier launch's id"
);
assert!(
!LaunchSpec::started_marker_in(dir.path(), ¤t).exists(),
"the pane has not run this launch yet, so its own sentinel must be absent \
even though a previous launch's survives"
);
}
#[test]
fn deliver_announces_a_refused_line_in_the_pane() {
let dir = tempfile::tempdir().expect("tempdir");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
let tmux = Recorder::new(1);
let err = deliver_in(&tmux, "tm-sess", None, &spec, dir.path())
.expect_err("a refused send must error");
assert!(
matches!(err, crate::runtime::RuntimeError::TmuxUnavailable(_)),
"{err}"
);
let lines = tmux.lines();
assert_eq!(lines.len(), 2, "the abort notice must follow: {lines:?}");
assert!(lines[1].contains("aborted"), "{:?}", lines[1]);
assert!(lines[1].contains(&spec.session_id), "{:?}", lines[1]);
let leftovers: Vec<_> = std::fs::read_dir(dir.path())
.expect("readdir")
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|e| e == "json"))
.collect();
assert!(
leftovers.is_empty(),
"an abandoned spec must be removed: {leftovers:?}"
);
}
#[cfg(unix)]
#[test]
fn deliver_errors_and_cleans_up_when_the_spec_dir_is_unwritable() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().expect("tempdir");
let blocked = dir.path().join("blocked");
std::fs::create_dir(&blocked).expect("mkdir");
std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o500)).expect("chmod");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
let tmux = Recorder::new(0);
let err = deliver_in(&tmux, "tm-sess", None, &spec, &blocked.join("nested"))
.expect_err("an unwritable spec dir must error");
assert!(
matches!(err, crate::runtime::RuntimeError::Spawn(_)),
"{err}"
);
let lines = tmux.lines();
assert_eq!(lines.len(), 1, "only the abort notice is typed: {lines:?}");
assert!(lines[0].contains("aborted"), "{:?}", lines[0]);
let _ = std::fs::set_permissions(&blocked, std::fs::Permissions::from_mode(0o700));
}
#[test]
fn deliver_refuses_a_wedged_pane_and_writes_no_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let cwd = PathBuf::from("/w");
let spec = spawn_spec(&bare_launch(&cwd, &[]));
let tmux = Recorder::wedged_forever();
let err = deliver_in(&tmux, "tm-sess", None, &spec, dir.path())
.expect_err("a pane that never executes what it is typed must refuse the launch");
let msg = err.to_string();
assert!(
msg.contains("refusing to launch into pane 'tm-sess'"),
"the refusal must name the pane and the reason: {msg}"
);
let written: Vec<_> = std::fs::read_dir(dir.path())
.expect("readdir")
.filter_map(Result::ok)
.map(|e| e.path())
.collect();
assert!(
written.is_empty(),
"a refused launch must leave no spec and no pointer behind — they carry \
credentials in cleartext: {written:?}"
);
let lines = tmux.lines();
assert_eq!(lines.len(), 1, "only the abort notice is typed: {lines:?}");
assert!(lines[0].contains("aborted"), "{:?}", lines[0]);
}