use crate::core::doctor::{CheckStatus, DoctorCheck};
pub fn check_tcc_taint() -> DoctorCheck {
let is_macos = cfg!(target_os = "macos");
let spi_available = crate::core::spawn_disclaim::disclaim_spi_available();
let disabled_by_env = crate::core::spawn_disclaim::disclaim_disabled_by_env();
build_tcc_taint_check(is_macos, spi_available, disabled_by_env)
}
fn build_tcc_taint_check(
is_macos: bool,
spi_available: bool,
disabled_by_env: bool,
) -> DoctorCheck {
if !is_macos {
return DoctorCheck::new(
"tcc_taint",
CheckStatus::Ok,
"not applicable on this platform (macOS TCC only)",
);
}
if disabled_by_env {
return DoctorCheck::new(
"tcc_taint",
CheckStatus::Warn,
"TM_DISABLE_SPAWN_DISCLAIM is set — managed panes launch `claude` WITHOUT the \
TCC disclaim, so its data-access prompts are attributed to the shared tmux \
server (issue #2997). Unset it to re-enable the disclaim.",
);
}
if !spi_available {
return DoctorCheck::new(
"tcc_taint",
CheckStatus::Warn,
"the macOS responsibility-disclaim SPI did not resolve on this build — managed \
panes cannot disclaim TCC responsibility, so `claude`'s data-access prompts may \
be attributed to the shared tmux server (issue #2997).",
);
}
DoctorCheck::new(
"tcc_taint",
CheckStatus::Ok,
"managed panes spawn `claude` with TCC responsibility disclaimed (issue #2997 \
attribution fix active). Note: a first-time data-access prompt may still appear — \
now attributed to Claude Code itself, not tmux/trusty-mpm — and can be granted once.",
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tcc_taint_ok_when_disclaim_active() {
let c = build_tcc_taint_check(true, true, false);
assert_eq!(c.status, CheckStatus::Ok);
assert_eq!(c.name, "tcc_taint");
assert!(c.message.contains("attribution fix active"));
}
#[test]
fn tcc_taint_warn_when_disabled() {
let c = build_tcc_taint_check(true, true, true);
assert_eq!(c.status, CheckStatus::Warn);
assert!(c.message.contains("TM_DISABLE_SPAWN_DISCLAIM"));
}
#[test]
fn tcc_taint_warn_when_spi_absent() {
let c = build_tcc_taint_check(true, false, false);
assert_eq!(c.status, CheckStatus::Warn);
assert!(c.message.contains("SPI"));
}
#[test]
fn tcc_taint_ok_on_non_macos() {
let c = build_tcc_taint_check(false, false, true);
assert_eq!(c.status, CheckStatus::Ok);
assert!(c.message.contains("not applicable"));
}
#[test]
fn tcc_taint_is_advisory_only() {
for (macos, spi, disabled) in [
(true, true, false),
(true, true, true),
(true, false, false),
(true, false, true),
(false, false, false),
] {
assert_ne!(
build_tcc_taint_check(macos, spi, disabled).status,
CheckStatus::Fail
);
}
}
}