trusty-mpm 1.7.3

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
//! RAII ownership of a REAL tmux session created by a test (#6116).
//!
//! Why: three fixtures in this crate shelled out to `tmux new-session` and
//! cleaned up with a bare `kill-session` placed after the code under test. That
//! kill is skipped whenever anything between the two panics, and one of those
//! fixtures panics by design — `wait_for_stable_dead_runtime` gives up at a 10s
//! deadline. tmux sessions are machine-global and outlive the test process, so
//! every panicked or cancelled run left one behind permanently: 29 `tm-deadrt*`
//! sessions accumulated on the owner's machine over 30 hours. #1790 isolated the
//! session STORE and said nothing about raw tmux spawns; this module is the
//! missing half.
//!
//! What: [`ScratchTmuxSession`] owns the session it created and kills it in
//! `Drop`, so normal return, `?`, assertion failure, and unwinding panic all
//! tear it down. It targets tmux with the `=<name>` EXACT-match form rather
//! than the bare `<name>` the old call sites used — a bare target falls through
//! to prefix and `fnmatch` matching, so `kill-session -t tm-deadrt99-01` will
//! happily destroy `tm-deadrt99-01-sibling` when the exact name is gone.
//! Verified on tmux 3.6b: with only the sibling alive, the bare form killed it
//! and the `=` form left it untouched.
//!
//! This file is compiled into TWO targets — the `trusty-mpm` lib and the `tm`
//! binary — through a `#[path]` include from each one's `test_support` module,
//! because a fixture reachable from only one of them would have to be written
//! twice, and a kill-on-drop guardrail that exists in two copies is one edit
//! away from drifting. The binary it drives is passed in rather than resolved
//! here for the same reason: `core::tmux::resolve_tmux_binary_or_bare` is
//! spelled `crate::…` in the lib and `trusty_mpm::…` in the bin, and no single
//! spelling compiles in both.
//!
//! **What `Drop` cannot cover, and what does.** A `Drop` guard runs on an
//! unwinding panic and on nothing else: a SIGKILL, a `cargo test` timeout, an
//! aborted run and a killed terminal all end the process without unwinding, so
//! the session survives. Three did on 2026-08-24. Two mechanisms cover that gap,
//! and neither is this guard:
//!
//! * The daemon refuses to adopt any session named under
//!   [`trusty_common::session_naming::RESERVED_TEST_PREFIX`] (#6116), so a
//!   leaked one never becomes a record or a picker row, and stays visible to
//!   `daemon::orphan_gc`, which kills an idle shell with no live child.
//!   [`reserved_session_name`] and [`reserved_project_slug`] are the two ways a
//!   fixture lands in that namespace — both build from the same constant the
//!   daemon reads, so the mint and the refusal cannot drift.
//! * [`ScratchTmuxSession::spawn`] sweeps stale reserved-namespace sessions
//!   once per test process, so the NEXT run on this machine reaps what the last
//!   one leaked. It is bounded by age
//!   ([`STALE_SESSION_AGE_SECS`]) so a concurrently running suite is never
//!   touched. What it does NOT do: reap anything before that next run happens,
//!   reap on a machine where the suite never runs again, or help at all if tmux
//!   is unreachable. It reduces how long a leak lives; the daemon-side refusal
//!   is what makes the leak harmless while it does.
//!
//! **Every tmux spawn here disclaims TCC responsibility (#7060).** They all go
//! through [`tmux_output`], which calls the crate's disclaiming spawn primitive
//! `core::spawn_disclaim::disclaimed_output` — the same one
//! `core::tmux::run_tmux_with_bin` uses for every production spawn. Until
//! #7060 this file spawned tmux itself, so the server it forked took its TCC
//! responsible process from the signed parent rather than becoming its own, and
//! macOS asked "tmux needs permission to access data from other apps" once per
//! test run: tmux's default `exit-empty` tears the server down with the last
//! session, so the next run forked a fresh, again-unrecognised one. The
//! primitive arrives as `super::tmux_spawn`, a re-export each `test_support`
//! supplies, because its own path is spelled `crate::…` in the lib and
//! `trusty_mpm::…` in the bin — the same two-spelling problem that makes the
//! tmux binary a parameter here.
//!
//! Test: [`tests`] below — a session that outlives a panicking closure is gone
//! afterwards, a guard never touches a name it did not create, the stale sweep
//! selects by age and namespace, and no raw process spawn survives in this
//! file.

use std::sync::Once;

/// Run `tmux_bin` with `args` through the crate's TCC-disclaiming spawn
/// primitive, capturing stdout and stderr (#7060).
///
/// Why: an undisclaimed tmux server inherits its TCC responsible process from
/// the signed binary that forked it, so macOS attributes whatever the pane's
/// login shell touches back to that binary and prompts the operator for it.
/// Production has routed every tmux spawn through the disclaim since #2819;
/// this fixture was the one path left out.
/// What: delegates to [`tmux_output_owned`], the single call site of
/// `super::tmux_spawn`.
/// Test: [`tests::the_fixture_spawn_seam_captures_output`],
/// [`tests::no_raw_process_spawn_survives_in_this_fixture`].
fn tmux_output(tmux_bin: &str, args: &[&str]) -> std::io::Result<std::process::Output> {
    let argv: Vec<String> = args.iter().map(|a| (*a).to_string()).collect();
    tmux_output_owned(tmux_bin, &argv)
}

/// [`tmux_output`] for an argv already built as owned strings.
///
/// `super::tmux_spawn` is each target's re-export of
/// `core::spawn_disclaim::disclaimed_output`, whose documented contract is
/// `.args(args).output()` — stdin from `/dev/null`, stdout and stderr captured
/// — plus the macOS disclaim attribute. Everything this file observes about a
/// tmux run is therefore unchanged by the routing.
///
/// #7996: the exec resolves a bare `tmux` through `$PATH`, so it holds the
/// target's one PATH lock (`super::lock_path_env`, see each `test_support`)
/// for the duration of the spawn. This is the single seam every tmux
/// invocation in this file passes through, `Drop`'s best-effort
/// `kill-session` included — which is the call the race was observed to eat.
/// Test: [`tests::the_spawn_seam_takes_the_path_lock`].
fn tmux_output_owned(tmux_bin: &str, args: &[String]) -> std::io::Result<std::process::Output> {
    let _path_env = super::lock_path_env();
    super::tmux_spawn(tmux_bin, args)
}

/// The global `-L <socket>` args a tmux invocation needs to reach a private
/// server instead of the machine-shared default one, or none at all.
///
/// See #7848: `a_session_outside_the_root_is_left_alone` and its siblings
/// spawn a real session on the shared default server to prove a guard leaves
/// it alone; a concurrent `tm` process sweeping or killing sessions on that
/// same server can remove it between the guard's drop and the assertion,
/// failing the test for a reason unrelated to the property it checks. Every
/// `*_on_socket` entry point below threads `socket` through to this helper so
/// a caller that supplies one gets a server nothing else on the host can
/// reach.
fn socket_prefix(socket: Option<&str>) -> Vec<String> {
    match socket {
        Some(name) => vec!["-L".to_string(), name.to_string()],
        None => Vec::new(),
    }
}

/// Age past which a reserved-namespace session is certainly leaked (#6116).
///
/// Why: no test in this suite holds a tmux session for anything close to half
/// an hour, so a survivor this old cannot belong to a run still in progress —
/// which is what makes the sweep safe to run while sibling test binaries and
/// other engineers' `cargo test` processes are using the same tmux server.
///
/// What it cannot tell apart: a tmux listing carries a name and a creation
/// time, no provenance. On a machine running this suite, a session the DAEMON
/// created for a project legitimately named `xtest-…` is killed here at 30
/// minutes like any other. The daemon-side rules avoid that class of mistake by
/// also requiring an adopted provenance
/// ([`crate::session_manager::SessionRecord::is_leaked_test_adoption`]); this
/// sweep has no record to ask.
const STALE_SESSION_AGE_SECS: i64 = 1_800;

/// Runs [`sweep_stale_reserved_sessions`] once per test process.
static SWEEP_ONCE: Once = Once::new();

/// A tmux session name in the namespace the daemon refuses to adopt (#6116).
///
/// What: `tm-xtest-<tag>-<pid>`, unique per process so the two targets this
/// file compiles into cannot collide in the machine-global tmux namespace when
/// cargo runs them concurrently.
/// Test: [`tests::a_minted_name_is_one_the_daemon_refuses`].
pub(crate) fn reserved_session_name(tag: &str) -> String {
    format!(
        "{}{tag}-{}",
        trusty_common::session_naming::RESERVED_TEST_PREFIX,
        std::process::id()
    )
}

/// The repo/project slug a fixture passes to `create_with_id` so the tmux name
/// the daemon DERIVES lands in the reserved namespace (#6116).
///
/// Why: a fixture that seeds a record does not choose its `tmux_name` —
/// `build_managed_session_name` derives `tm-<project>-NN` from the repo url. So
/// the namespace has to be reached through the project slug, not by naming the
/// session directly.
/// What: the reserved prefix with `tm-` and the trailing dash removed
/// (`xtest`), joined to `tag`. Feed it as the repo segment of a url and the
/// derived name is `tm-xtest-<tag>-NN`.
/// Test: [`tests::a_derived_project_slug_yields_a_reserved_name`]; end to end
/// by `session_resume_headless_dead_runtime_reconciles_and_restarts`, which
/// asserts the name it actually got is reserved.
pub(crate) fn reserved_project_slug(tag: &str) -> String {
    let leaf = trusty_common::session_naming::RESERVED_TEST_PREFIX
        .strip_prefix(trusty_common::session_naming::PREFIX)
        .unwrap_or(trusty_common::session_naming::RESERVED_TEST_PREFIX)
        .trim_end_matches('-');
    format!("{leaf}-{tag}")
}

/// Pick the reserved-namespace sessions in a `tmux list-sessions` listing that
/// are older than `max_age_secs`.
///
/// Why: the selection is the whole risk in the sweep — killing a session a
/// concurrent run still needs would be worse than the leak it cleans up — so it
/// is a pure function over the listing text, testable without a tmux server.
/// What: reads `<created-epoch> <session-name>` lines (the format
/// [`sweep_stale_reserved_sessions`] requests, created first because a session
/// name may contain spaces), and keeps names in the reserved namespace whose
/// age exceeds `max_age_secs`. An unparseable line is skipped, never killed.
/// Test: [`tests::stale_sweep_selects_only_old_reserved_sessions`].
fn stale_reserved_names(listing: &str, now_epoch: i64, max_age_secs: i64) -> Vec<String> {
    listing
        .lines()
        .filter_map(|line| line.trim_end().split_once(' '))
        .filter_map(|(created, name)| Some((created.trim().parse::<i64>().ok()?, name)))
        .filter(|(created, name)| {
            now_epoch - created > max_age_secs
                && trusty_common::session_naming::is_reserved_test_session_name(name)
        })
        .map(|(_, name)| name.to_string())
        .collect()
}

/// Kill reserved-namespace sessions left behind by an earlier, hard-killed test
/// process (#6116).
///
/// Why/What: see this module's docs — `Drop` cannot run after a SIGKILL, so the
/// next run cleans up instead. Best-effort throughout: a tmux that will not
/// answer (no server yet, no binary) simply sweeps nothing.
/// Test: the selection half via [`stale_reserved_names`]; the kill half is the
/// same `kill-session -t =<name>` call [`ScratchTmuxSession::drop`] makes.
fn sweep_stale_reserved_sessions(tmux_bin: &str) {
    let Ok(out) = tmux_output(
        tmux_bin,
        &["list-sessions", "-F", "#{session_created} #{session_name}"],
    ) else {
        return;
    };
    if !out.status.success() {
        return;
    }
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_or(0, |d| d.as_secs() as i64);
    for name in stale_reserved_names(
        &String::from_utf8_lossy(&out.stdout),
        now,
        STALE_SESSION_AGE_SECS,
    ) {
        eprintln!("test-support: killing leaked test tmux session '{name}' (#6116)");
        let _ = tmux_output(
            tmux_bin,
            &[
                "kill-session",
                "-t",
                &trusty_common::tmux::exact_session_target(&name),
            ],
        );
    }
}

/// A real tmux session owned by a test, killed when this value drops.
///
/// Why: see the module docs — the panic path, not the happy path, is the whole
/// point.
/// What: [`ScratchTmuxSession::spawn`] creates the session and takes ownership
/// of exactly the name it passed to `new-session`; `Drop` kills that one name.
/// A failed `new-session` panics instead of yielding a guard, so a guard never
/// claims a session it did not create.
/// Test: [`tests::drop_kills_the_session_even_when_the_body_panics`],
/// [`tests::drop_kills_only_the_exact_name_it_created`].
pub(crate) struct ScratchTmuxSession {
    tmux_bin: String,
    /// `-L` target this guard's own tmux calls address, or `None` for the
    /// machine-shared default server. See #7848.
    socket: Option<String>,
    name: String,
}

impl ScratchTmuxSession {
    /// Create `name` as a detached tmux session running `pane_command`.
    ///
    /// Panics if `new-session` cannot be spawned or exits non-zero. A duplicate
    /// name is one such non-zero exit, which is the behaviour that keeps the
    /// ownership claim honest: the caller gets no guard, so nothing later kills
    /// a session someone else created.
    ///
    /// The panic quotes tmux's own stderr, because the exit status alone does
    /// not distinguish the cases (#6523). `create window failed: fork failed:
    /// Device not configured` means the HOST is out of pseudo-terminals — on
    /// macOS the pool is capped by `kern.tty.ptmx_max` (511) and a machine with
    /// hundreds of leaked panes exhausts it — so every tmux spawn on that
    /// machine fails until the panes are reaped. That is a machine fault, not a
    /// fixture one; no test-side change creates a pty.
    ///
    /// #7848: the lib's only caller is
    /// `core::process::tests::claude_pid_resolves_through_disclaim_wrapper`,
    /// which is `#[cfg(target_os = "macos")]` — so a lib-crate compilation on
    /// a non-macOS host (CI's Linux runner) has no reachable caller and sees
    /// this as unused. The `tm` binary's own tests call it unconditionally.
    /// See the module docs on the two-target `#[path]` split this file
    /// already lives with.
    #[allow(dead_code)]
    pub(crate) fn spawn(tmux_bin: &str, name: &str, pane_command: &str) -> Self {
        Self::spawn_in(tmux_bin, name, None, pane_command)
    }

    /// [`ScratchTmuxSession::spawn`] with the pane's working directory pinned.
    ///
    /// Why: [`FixtureTmuxSessions`] claims a session by where its pane sits, so
    /// its own tests need to place a session inside — and outside — a fixture
    /// root on purpose (#6542). Every other caller wants the inherited cwd and
    /// keeps [`ScratchTmuxSession::spawn`].
    /// What: adds `-c <cwd>` when `cwd` is `Some`; otherwise identical.
    /// Test: [`tests::a_session_opened_under_the_root_is_killed_on_drop`].
    ///
    /// #7848: reachable in the lib only through [`Self::spawn`], which is
    /// itself lib-dead on a non-macOS compilation — see its doc comment. See
    /// the module docs on the two-target `#[path]` split this file already
    /// lives with.
    #[allow(dead_code)]
    pub(crate) fn spawn_in(
        tmux_bin: &str,
        name: &str,
        cwd: Option<&std::path::Path>,
        pane_command: &str,
    ) -> Self {
        Self::spawn_in_on_socket(tmux_bin, None, name, cwd, pane_command)
    }

    /// [`ScratchTmuxSession::spawn`] against a private tmux server rather than
    /// the shared default one.
    ///
    /// See #7848: threads `socket` into the same `-L` position every other
    /// call in this fixture uses, so a caller that scopes its own tests to a
    /// private server (e.g. `PrivateTmuxServer` in [`tests`]) never touches
    /// the machine-shared one.
    pub(crate) fn spawn_on_socket(
        tmux_bin: &str,
        socket: Option<&str>,
        name: &str,
        pane_command: &str,
    ) -> Self {
        Self::spawn_in_on_socket(tmux_bin, socket, name, None, pane_command)
    }

    /// [`ScratchTmuxSession::spawn_in`] against `socket`, or the shared default
    /// server when `socket` is `None`. The core implementation every other
    /// `spawn*` entry point on this type delegates to.
    pub(crate) fn spawn_in_on_socket(
        tmux_bin: &str,
        socket: Option<&str>,
        name: &str,
        cwd: Option<&std::path::Path>,
        pane_command: &str,
    ) -> Self {
        // #6116: reap what an earlier hard-killed run leaked, before adding to
        // the namespace. Once per process, mirroring `test_support`'s
        // `sweep_stale_test_dirs`. The sweep targets the shared default
        // server, so it is skipped entirely for a private-socket spawn — see
        // #7848.
        if socket.is_none() {
            SWEEP_ONCE.call_once(|| sweep_stale_reserved_sessions(tmux_bin));
        }
        let mut args: Vec<String> = socket_prefix(socket);
        args.extend(
            ["new-session", "-d", "-s", name]
                .iter()
                .map(|s| (*s).to_string()),
        );
        if let Some(dir) = cwd {
            args.push("-c".to_string());
            args.push(dir.to_string_lossy().into_owned());
        }
        args.push(pane_command.to_string());
        // #6523: capture tmux's stderr instead of letting it through. `.status()`
        // sent the real cause to the test binary's stderr, unattached to the
        // panic, so five simultaneous failures each read only `exit status: 1`
        // and got misattributed to nested tmux.
        // #7060: the disclaiming primitive, so the server this forks is its own
        // TCC responsible process instead of the signed parent's.
        let out = tmux_output_owned(tmux_bin, &args)
            .unwrap_or_else(|e| panic!("spawn `{tmux_bin} new-session -s {name}`: {e}"));
        assert!(
            out.status.success(),
            "`{tmux_bin} new-session -d -s {name}` failed with {}; the session was NOT \
             created, so no guard may claim it. tmux said: {}",
            out.status,
            String::from_utf8_lossy(&out.stderr).trim()
        );
        Self {
            tmux_bin: tmux_bin.to_string(),
            socket: socket.map(str::to_string),
            name: name.to_string(),
        }
    }

    /// The exact session name this guard owns.
    pub(crate) fn name(&self) -> &str {
        &self.name
    }

    /// Whether a session named exactly `name` is live right now.
    ///
    /// `=` pins tmux to an exact match; see the module docs for why the bare
    /// form is unsafe here.
    ///
    /// #7848: only the `tm` binary's own tests call this form now — the
    /// lib's tests in this file moved to [`Self::exists_on_socket`] — so the
    /// lib-crate compilation of this shared file sees it as unused. See the
    /// module docs on the two-target `#[path]` split this file already lives
    /// with.
    #[allow(dead_code)]
    pub(crate) fn exists(tmux_bin: &str, name: &str) -> bool {
        Self::exists_on_socket(tmux_bin, None, name)
    }

    /// [`ScratchTmuxSession::exists`] against `socket` rather than the shared
    /// default server (#7848).
    pub(crate) fn exists_on_socket(tmux_bin: &str, socket: Option<&str>, name: &str) -> bool {
        let mut args = socket_prefix(socket);
        args.push("has-session".to_string());
        args.push("-t".to_string());
        args.push(trusty_common::tmux::exact_session_target(name));
        tmux_output_owned(tmux_bin, &args).is_ok_and(|out| out.status.success())
    }

    /// Whether `tmux_bin` runs at all, for fixtures that skip rather than fail
    /// where tmux is absent (the convention `core::process`'s ignored
    /// disclaim-wrapper test already follows).
    pub(crate) fn tmux_available(tmux_bin: &str) -> bool {
        tmux_output(tmux_bin, &["-V"]).is_ok_and(|out| out.status.success())
    }
}

impl Drop for ScratchTmuxSession {
    fn drop(&mut self) {
        // Best-effort by construction: `Drop` runs during unwinding, where a
        // panic would abort the process and bury the original failure.
        let mut args = socket_prefix(self.socket.as_deref());
        args.push("kill-session".to_string());
        args.push("-t".to_string());
        args.push(trusty_common::tmux::exact_session_target(&self.name));
        let _ = tmux_output_owned(&self.tmux_bin, &args);
    }
}

/// Live session names, or an empty set when tmux will not answer.
///
/// `socket` addresses a private server via `-L` (#7848); `None` reaches the
/// shared default one.
fn live_session_names(tmux_bin: &str, socket: Option<&str>) -> std::collections::BTreeSet<String> {
    let mut args = socket_prefix(socket);
    args.push("list-sessions".to_string());
    args.push("-F".to_string());
    args.push("#{session_name}".to_string());
    let Ok(out) = tmux_output_owned(tmux_bin, &args) else {
        return std::collections::BTreeSet::new();
    };
    if !out.status.success() {
        return std::collections::BTreeSet::new();
    }
    String::from_utf8_lossy(&out.stdout)
        .lines()
        .map(|l| l.trim_end().to_string())
        .filter(|l| !l.is_empty())
        .collect()
}

/// Names of live sessions holding at least one pane whose working directory is
/// inside `root`.
///
/// What: reads `<session-name>\t<pane-path>` from `list-panes -a`. A line that
/// does not split on the tab is SKIPPED, never selected — an unparseable line
/// leaves a session alive rather than killing one this guard cannot identify.
/// `Path::starts_with` compares whole components, so `/tmp/ab` does not match a
/// pane sitting in `/tmp/abc`.
/// Test: [`tests::a_session_outside_the_root_is_left_alone`].
fn sessions_with_pane_under(
    tmux_bin: &str,
    socket: Option<&str>,
    root: &std::path::Path,
) -> std::collections::BTreeSet<String> {
    let mut args = socket_prefix(socket);
    args.push("list-panes".to_string());
    args.push("-a".to_string());
    args.push("-F".to_string());
    args.push("#{session_name}\t#{pane_current_path}".to_string());
    let Ok(out) = tmux_output_owned(tmux_bin, &args) else {
        return std::collections::BTreeSet::new();
    };
    if !out.status.success() {
        return std::collections::BTreeSet::new();
    }
    String::from_utf8_lossy(&out.stdout)
        .lines()
        .filter_map(|line| line.trim_end().split_once('\t'))
        .filter(|(_, pane_path)| std::path::Path::new(pane_path).starts_with(root))
        .map(|(name, _)| name.to_string())
        .collect()
}

/// Sessions the CODE UNDER TEST created inside a fixture directory, killed when
/// this value drops (#6542).
///
/// Why: [`ScratchTmuxSession`] guards a session the TEST spawns and therefore
/// names. The guided-fallback tests spawn none — they call
/// `commands::guided::fallback_protected`, which provisions a worktree and
/// launches a session whose name is derived from a session uuid the test never
/// sees. Two such sessions survived every run of `tests_behavior_b_tests`; 456
/// accumulated on the owner's machine over five days and exhausted its
/// pseudo-terminal pool (#6523).
///
/// What: snapshots the live session names at construction, and on drop kills
/// every session that is BOTH absent from that snapshot AND holding a pane
/// whose working directory is inside `root`. Both conditions are load-bearing —
/// the snapshot alone would claim a session a concurrently running suite
/// created, and the path alone would claim one this process created before the
/// guard existed. `root` is canonicalized at construction because tmux reports
/// `/private/var/…` where a macOS `TempDir` hands back `/var/…`.
///
/// What `Drop` cannot cover is the same gap [`ScratchTmuxSession`]'s module docs
/// describe: a SIGKILL or an aborted run ends the process without unwinding.
/// The sessions this guard reaps are named by the daemon's ordinary
/// `tm-<uuid>` scheme, not the reserved test namespace, so
/// [`sweep_stale_reserved_sessions`] does not reap them on the next run either.
///
/// Test: [`tests::a_session_opened_under_the_root_is_killed_on_drop`],
/// [`tests::a_session_outside_the_root_is_left_alone`],
/// [`tests::a_session_predating_the_guard_is_left_alone`].
pub(crate) struct FixtureTmuxSessions {
    tmux_bin: String,
    /// `-L` target this guard's own tmux calls address, or `None` for the
    /// machine-shared default server. See #7848.
    socket: Option<String>,
    root: std::path::PathBuf,
    preexisting: std::collections::BTreeSet<String>,
}

impl FixtureTmuxSessions {
    /// Start owning any session that appears under `root` from now on.
    ///
    /// #7848: only the `tm` binary's own tests call this form now — the
    /// lib's tests in this file moved to [`Self::watch_on_socket`] — so the
    /// lib-crate compilation of this shared file sees it as unused. See the
    /// module docs on the two-target `#[path]` split this file already lives
    /// with.
    #[allow(dead_code)]
    pub(crate) fn watch(tmux_bin: &str, root: &std::path::Path) -> Self {
        Self::watch_on_socket(tmux_bin, None, root)
    }

    /// [`Self::watch`] against a private tmux server rather than the shared
    /// default one (#7848).
    pub(crate) fn watch_on_socket(
        tmux_bin: &str,
        socket: Option<&str>,
        root: &std::path::Path,
    ) -> Self {
        Self {
            tmux_bin: tmux_bin.to_string(),
            socket: socket.map(str::to_string),
            root: root.canonicalize().unwrap_or_else(|_| root.to_path_buf()),
            preexisting: live_session_names(tmux_bin, socket),
        }
    }

    /// The sessions this guard currently owns — new since [`Self::watch`] and
    /// rooted under the fixture directory.
    pub(crate) fn spawned(&self) -> Vec<String> {
        sessions_with_pane_under(&self.tmux_bin, self.socket.as_deref(), &self.root)
            .into_iter()
            .filter(|name| !self.preexisting.contains(name))
            .collect()
    }
}

impl Drop for FixtureTmuxSessions {
    fn drop(&mut self) {
        for name in self.spawned() {
            eprintln!("test-support: killing fixture tmux session '{name}' (#6542)");
            // Best-effort: `Drop` runs during unwinding, where a panic would
            // abort the process and bury the original failure.
            let mut args = socket_prefix(self.socket.as_deref());
            args.push("kill-session".to_string());
            args.push("-t".to_string());
            args.push(trusty_common::tmux::exact_session_target(&name));
            let _ = tmux_output_owned(&self.tmux_bin, &args);
        }
    }
}

/// A private tmux server for exactly one test, addressed by `-L <name>`
/// so no other process on the host — a concurrent `tm` daemon, a sibling
/// test binary, another engineer's `cargo test` run — can see, adopt, or
/// kill what this test spawns.
///
/// Why: `a_session_outside_the_root_is_left_alone` and its siblings spawn
/// a REAL session on the shared default tmux server to prove a guard
/// leaves it alone. Observed 2026-09-14: a concurrent `tm` process swept
/// or killed that session between the guard's drop and the assertion,
/// failing the test for a reason unrelated to the property it checks. A
/// private-per-test socket removes the shared surface entirely (#7848).
/// What: mints a socket name from the same reserved-test constant
/// [`reserved_session_name`] uses, unique per test and process so
/// concurrent test binaries never collide, and tears down the WHOLE
/// server — not just the sessions inside it — with `-L <name>
/// kill-server` on drop. Best-effort: a server that never started (no
/// session spawned before an early panic) yields a harmless failing
/// `kill-server`.
/// Test: every test below that spawns a real tmux session passes this
/// fixture's `name()` as `-L` to every tmux call it makes.
///
/// #8443: lifted out of `mod tests` so production-path tests in both targets
/// can reuse it; [`PrivateTmuxServer::shim_bin`] lets code that only accepts
/// a tmux binary path reach this server.
pub(crate) struct PrivateTmuxServer {
    tmux_bin: String,
    socket: String,
    shim_dir: tempfile::TempDir,
}

#[allow(dead_code)] // Two targets include this file; each uses a subset.
impl PrivateTmuxServer {
    pub(crate) fn new(tmux_bin: &str, tag: &str) -> Self {
        let nanos = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_or(0, |d| d.subsec_nanos());
        Self {
            tmux_bin: tmux_bin.to_string(),
            socket: format!(
                "{}sock-{tag}-{}-{nanos}",
                trusty_common::session_naming::RESERVED_TEST_PREFIX,
                std::process::id()
            ),
            shim_dir: tempfile::tempdir().expect("shim dir"),
        }
    }

    /// The `-L` target every tmux call in this test must pass.
    pub(crate) fn name(&self) -> &str {
        &self.socket
    }

    /// Path to an executable that runs `tmux -L <socket> "$@"`, for code under
    /// test that takes a tmux binary path and adds no `-L` itself (#8443). It
    /// never reaches the default server.
    pub(crate) fn shim_bin(&self) -> String {
        use std::os::unix::fs::PermissionsExt;
        let path = self.shim_dir.path().join("tmux-private");
        if !path.exists() {
            let script = format!(
                "#!/bin/sh\nexec '{}' -L '{}' \"$@\"\n",
                self.tmux_bin, self.socket
            );
            std::fs::write(&path, script).expect("write tmux shim");
            std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755))
                .expect("chmod tmux shim");
        }
        path.to_string_lossy().into_owned()
    }

    /// Run `args` on this server; trimmed stdout, or `None` on a non-zero exit.
    pub(crate) fn query(&self, args: &[&str]) -> Option<String> {
        let mut argv = socket_prefix(Some(&self.socket));
        argv.extend(args.iter().map(|a| (*a).to_string()));
        let out = tmux_output_owned(&self.tmux_bin, &argv).ok()?;
        out.status
            .success()
            .then(|| String::from_utf8_lossy(&out.stdout).trim().to_string())
    }
}

impl Drop for PrivateTmuxServer {
    fn drop(&mut self) {
        // See #7848: best-effort — `kill-server` fails harmlessly when no
        // session was ever spawned on this socket.
        let _ = tmux_output(&self.tmux_bin, &["-L", &self.socket, "kill-server"]);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Bare `tmux`, matching what `core::tmux::resolve_tmux_binary_or_bare`
    /// falls back to. These tests only need the `PATH` lookup a `cargo test`
    /// shell already has.
    const TMUX: &str = "tmux";

    /// Process-unique, so the two targets this file compiles into cannot
    /// collide in the machine-global tmux namespace when cargo runs them
    /// concurrently.
    ///
    /// #6116: minted through [`reserved_session_name`] — these tests spawn real
    /// sessions, so a hard kill leaks THEM too, and the leak has to land in the
    /// namespace the daemon refuses to adopt.
    fn scratch_name(tag: &str) -> String {
        let nanos = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_or(0, |d| d.subsec_nanos());
        reserved_session_name(&format!("guard-{tag}-{nanos}"))
    }

    /// The spawn seam this file routes every tmux invocation through is a
    /// working capture path, exercised end to end (#7060).
    ///
    /// What this does and does not prove: on macOS `super::tmux_spawn` is
    /// `core::spawn_disclaim::disclaimed_output`, so this call really executes
    /// the `posix_spawnp` + `responsibility_spawnattrs_setdisclaim` path. The
    /// disclaim itself sets a spawn attribute with no effect on the child's
    /// exit status or output, so no assertion here can observe it — that the
    /// seam IS the primitive is fixed at compile time by each `test_support`'s
    /// re-export, and the absence of any other spawn by
    /// [`no_raw_process_spawn_survives_in_this_fixture`].
    #[test]
    fn the_fixture_spawn_seam_captures_output() {
        let out = tmux_output("/bin/echo", &["disclaim-seam"]).expect("spawn /bin/echo");
        assert!(out.status.success(), "echo exited {}", out.status);
        assert_eq!(
            String::from_utf8_lossy(&out.stdout).trim(),
            "disclaim-seam",
            "the seam must capture stdout the way `.output()` does"
        );
    }

    /// #7060, inverted into an assertion: a hand-rolled process spawn anywhere
    /// in this file forks an undisclaimed tmux server, which is the defect.
    ///
    /// Asserted against this file's own source because the property is "no
    /// other spawn exists", which no runtime call can demonstrate. The needle
    /// is split so this test is not itself a match.
    #[test]
    fn no_raw_process_spawn_survives_in_this_fixture() {
        let src = include_str!("test_tmux_session.rs");
        let needle = concat!("Command", "::new");
        assert_eq!(
            src.matches(needle).count(),
            0,
            "a raw process spawn is back in this fixture — the tmux server it forks takes \
             its TCC responsible process from the signed parent and macOS prompts the \
             operator once per test run (#7060). Route it through `tmux_output` instead."
        );
    }

    /// #7996, asserted against this file's own source for the same reason
    /// [`no_raw_process_spawn_survives_in_this_fixture`] is: the property is
    /// "every tmux exec in this file holds the PATH lock", and the one seam
    /// that could stop holding it is a single-line deletion no runtime call
    /// would notice — the race it closes needs a concurrent `set_var("PATH")`
    /// to land inside the exec.
    #[test]
    fn the_spawn_seam_takes_the_path_lock() {
        let src = include_str!("test_tmux_session.rs");
        assert!(
            src.contains("let _path_env = super::lock_path_env();"),
            "`tmux_output_owned` must hold the target's PATH lock across the exec — a bare \
             `tmux` is resolved through `$PATH`, and a concurrent `set_var` tears that read \
             (#7996)"
        );
    }

    /// The mint and the daemon's refusal read the same constant, so a fixture
    /// cannot drift out of the namespace that protects it.
    #[test]
    fn a_minted_name_is_one_the_daemon_refuses() {
        let name = reserved_session_name("mint");
        assert!(trusty_common::session_naming::is_reserved_test_session_name(&name));
        // Still managed, so the orphan-GC keeps its first gate on the pane.
        assert!(trusty_common::session_naming::is_managed_session_name(
            &name
        ));
    }

    /// A fixture that reaches the namespace through a derived name gets there
    /// too: `tm-<project>-NN` built from the slug is reserved.
    #[test]
    fn a_derived_project_slug_yields_a_reserved_name() {
        let derived = trusty_common::session_naming::build_managed_session_name(
            Some(&reserved_project_slug("deadrt1234")),
            std::path::Path::new("/tmp"),
            &[],
        )
        .expect("derive a name");
        assert!(
            trusty_common::session_naming::is_reserved_test_session_name(&derived),
            "a fixture's derived name must land in the reserved namespace, got {derived}"
        );
    }

    /// The sweep's whole risk is what it selects: only the reserved namespace,
    /// only past the age bound, and never a line it could not parse.
    #[test]
    fn stale_sweep_selects_only_old_reserved_sessions() {
        let now = 1_000_000;
        let listing = "\
900000 tm-xtest-deadrt1-01
999999 tm-xtest-running-now-01
900000 tm-trusty-tools-01
900000 work
not-an-epoch tm-xtest-garbage-01
";
        assert_eq!(
            stale_reserved_names(listing, now, STALE_SESSION_AGE_SECS),
            vec!["tm-xtest-deadrt1-01".to_string()],
            "only the aged, reserved, parseable session may be killed"
        );
    }

    /// The defect #6116 reports, inverted into an assertion: the fixture panics
    /// mid-test — exactly what `wait_for_stable_dead_runtime`'s 10s deadline
    /// does — and the session must still be gone afterwards.
    ///
    /// The panic message reaching stderr during this run is expected output,
    /// not a failure.
    ///
    /// #6523: the spawn and the precondition sit OUTSIDE the `catch_unwind`. In
    /// the earlier shape both were inside it, so a spawn that FAILED was caught
    /// by the same handler that the deliberate panic was meant to trip:
    /// `outcome.is_err()` held for the wrong reason, and `!exists` then held
    /// trivially because nothing had been created. The test reported `ok` on a
    /// machine that could not spawn a tmux session at all, while its two
    /// siblings failed — a false green that hid a third of the evidence.
    #[test]
    fn drop_kills_the_session_even_when_the_body_panics() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        // See #7848: a private server so a concurrent process on the host
        // cannot touch the session this test spawns. Declared first so it
        // drops LAST, after every guard that addresses it.
        let server = PrivateTmuxServer::new(TMUX, "panic");
        let name = scratch_name("panic");
        let session = ScratchTmuxSession::spawn_on_socket(TMUX, Some(server.name()), &name, "sh");
        assert!(
            ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), session.name()),
            "fixture precondition: the session must be live before the panic"
        );
        let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
            // Moved in, so the guard drops during THIS unwind — the property
            // under test.
            let _owned = session;
            panic!("simulated mid-test failure while the guard is alive");
        }));
        assert!(
            outcome.is_err(),
            "the closure must actually have panicked, or this proves nothing"
        );
        assert!(
            !ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &name),
            "session '{name}' survived a panicking test — this is #6116, where a \
             post-hoc kill-session placed after the code under test never ran"
        );
    }

    /// The guard must kill the one name it created and nothing else. A bare
    /// `-t <name>` target does not satisfy this: with the exact name already
    /// gone, tmux prefix-matches and kills `<name>-sibling` instead.
    #[test]
    fn drop_kills_only_the_exact_name_it_created() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        let server = PrivateTmuxServer::new(TMUX, "exact"); // See #7848
        let owned_name = scratch_name("exact");
        let sibling_name = format!("{owned_name}-sibling");
        let sibling =
            ScratchTmuxSession::spawn_on_socket(TMUX, Some(server.name()), &sibling_name, "sh");
        {
            let _owned =
                ScratchTmuxSession::spawn_on_socket(TMUX, Some(server.name()), &owned_name, "sh");
        }
        assert!(
            !ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &owned_name),
            "the guard must kill its own session on drop"
        );
        assert!(
            ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &sibling_name),
            "'{sibling_name}' shares a prefix with the dropped guard's name but was created \
             by someone else; a bare `-t` target would have killed it"
        );
        drop(sibling);
    }

    /// The defect #6542 reports, inverted: a session the test did not name,
    /// sitting inside the fixture root, must be gone once the guard drops.
    ///
    /// The `owner` guard is a safety net, not the subject — if
    /// [`FixtureTmuxSessions`] were broken, this test must still not leak.
    #[test]
    fn a_session_opened_under_the_root_is_killed_on_drop() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        let server = PrivateTmuxServer::new(TMUX, "under"); // See #7848
        let root = tempfile::tempdir().expect("fixture root");
        let name = scratch_name("under");
        let guard = FixtureTmuxSessions::watch_on_socket(TMUX, Some(server.name()), root.path());
        let owner = ScratchTmuxSession::spawn_in_on_socket(
            TMUX,
            Some(server.name()),
            &name,
            Some(root.path()),
            "sh",
        );
        assert_eq!(
            guard.spawned(),
            vec![name.clone()],
            "the guard must claim the session that appeared under its root"
        );
        drop(guard);
        assert!(
            !ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &name),
            "session '{name}' survived the guard — this is #6542, where the \
             guided-fallback tests left one tm-<uuid> session behind per run"
        );
        drop(owner);
    }

    /// The guard's claim is bounded by the fixture root: a session that a
    /// concurrent suite opens elsewhere is neither claimed nor killed.
    #[test]
    fn a_session_outside_the_root_is_left_alone() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        // See #7848: scoped to a private server, declared first so it drops
        // LAST — no other process on the host can sweep or kill the session
        // this test leaves alive between the guard's drop and the assertion
        // below.
        let server = PrivateTmuxServer::new(TMUX, "outside");
        let root = tempfile::tempdir().expect("fixture root");
        let elsewhere = tempfile::tempdir().expect("unrelated dir");
        let name = scratch_name("outside");
        let guard = FixtureTmuxSessions::watch_on_socket(TMUX, Some(server.name()), root.path());
        let sibling = ScratchTmuxSession::spawn_in_on_socket(
            TMUX,
            Some(server.name()),
            &name,
            Some(elsewhere.path()),
            "sh",
        );
        assert!(
            guard.spawned().is_empty(),
            "a session outside the fixture root is not this guard's to claim"
        );
        drop(guard);
        assert!(
            ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &name),
            "'{name}' sits outside the fixture root and belongs to someone else"
        );
        drop(sibling);
    }

    /// A session already live when the guard is created is not the guard's,
    /// even when it sits inside the root — the snapshot is what makes the
    /// ownership claim honest.
    #[test]
    fn a_session_predating_the_guard_is_left_alone() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        let server = PrivateTmuxServer::new(TMUX, "predates"); // See #7848
        let root = tempfile::tempdir().expect("fixture root");
        let name = scratch_name("predates");
        let earlier = ScratchTmuxSession::spawn_in_on_socket(
            TMUX,
            Some(server.name()),
            &name,
            Some(root.path()),
            "sh",
        );
        let guard = FixtureTmuxSessions::watch_on_socket(TMUX, Some(server.name()), root.path());
        assert!(
            guard.spawned().is_empty(),
            "a session that predates the guard is not new, so not the guard's"
        );
        drop(guard);
        assert!(
            ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &name),
            "'{name}' predates the guard and must survive it"
        );
        drop(earlier);
    }

    /// A guard is only handed out for a session that was actually created, so
    /// a name already taken yields a panic rather than an ownership claim over
    /// someone else's session.
    #[test]
    fn spawning_a_duplicate_name_panics_instead_of_claiming_it() {
        if !ScratchTmuxSession::tmux_available(TMUX) {
            eprintln!("tmux not available; skipping");
            return;
        }
        let server = PrivateTmuxServer::new(TMUX, "dup"); // See #7848
        let name = scratch_name("dup");
        let owner = ScratchTmuxSession::spawn_on_socket(TMUX, Some(server.name()), &name, "sh");
        let second = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            ScratchTmuxSession::spawn_on_socket(TMUX, Some(server.name()), &name, "sh")
        }));
        assert!(
            second.is_err(),
            "a duplicate `new-session` must panic, not hand out a second owner"
        );
        assert!(
            ScratchTmuxSession::exists_on_socket(TMUX, Some(server.name()), &name),
            "the failed spawn must leave the original owner's session untouched"
        );
        drop(owner);
        assert!(!ScratchTmuxSession::exists_on_socket(
            TMUX,
            Some(server.name()),
            &name
        ));
    }
}