Skip to content

Commit e902910

Browse files
committed
Stop the doctor reporting its own permissions as the app's
TCC pins grants per bundle, and the doctor is its own bundle (OutLoudDoctor.app). Both permission checks answered for the process running them, then presented that as the app's state. Today it reported "not trusted for accessibility" and "no Input Monitoring access" with a FAIL verdict while OutLoud.app had both grants. I believed it, sent the user to System Settings, and spent an hour on a permission that was already granted. A confident wrong answer is worse than no answer, because it stops the search. Both checks now say whose grant they are describing and point at `outloud --permissions`, which asks the only process whose answer matters. The verdict drops from FAIL to WARN, so a healthy machine no longer looks broken. The shell-launch path is untouched: that warning already explained the terminal's grant is the one in effect, and it is correct.
1 parent 4062048 commit e902910

1 file changed

Lines changed: 37 additions & 0 deletions

File tree

‎crates/diag/src/checks.rs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@ use crate::{Check, CheckOutcome, Env, ErrorClass};
2424
/// run from a shell is judged against the terminal, so the app's own toggle
2525
/// can read "on" while every call fails. We therefore report not just the
2626
/// trust bit but *who* is being judged, by looking at how we were launched.
27+
/// Whether the running process is the doctor's own bundle rather than the
28+
/// app being diagnosed.
29+
///
30+
/// TCC grants are pinned per bundle, so OutLoudDoctor.app answering "am I
31+
/// trusted" describes itself, not OutLoud.app. Presenting that as the app's
32+
/// permission state produces a confident wrong answer, which is worse than
33+
/// no answer: I acted on one today and spent an hour chasing a permission
34+
/// that was already granted.
35+
fn is_separate_doctor_bundle() -> bool {
36+
std::env::current_exe()
37+
.map(|p| p.to_string_lossy().contains("OutLoudDoctor.app"))
38+
.unwrap_or(false)
39+
}
40+
2741
pub struct AccessibilityPermission;
2842

2943
impl Check for AccessibilityPermission {
@@ -68,6 +82,19 @@ impl Check for AccessibilityPermission {
6882
}
6983
return CheckOutcome::pass("process is trusted for accessibility");
7084
}
85+
// TCC grants are per-bundle, and the doctor is its own bundle
86+
// (OutLoudDoctor.app). A FAIL here says nothing about whether
87+
// OutLoud.app is trusted, and reading it as if it did cost a long
88+
// detour today: the doctor reported both permissions missing while
89+
// the app had both.
90+
if !launched_from_shell && is_separate_doctor_bundle() {
91+
return CheckOutcome::warn(
92+
"this is the DOCTOR's own grant, not the app's: TCC pins \
93+
permissions per bundle and these are different bundles",
94+
ErrorClass::Configuration,
95+
"ask the app itself: dist/OutLoud.app/Contents/MacOS/OutLoud --permissions",
96+
);
97+
}
7198
if launched_from_shell {
7299
return CheckOutcome::fail(
73100
"not trusted, and launched from a shell: macOS is checking the TERMINAL'S \
@@ -920,6 +947,16 @@ impl Check for InputMonitoringPermission {
920947
if !cfg!(target_os = "macos") {
921948
return CheckOutcome::pass("not macOS: no Input Monitoring grant exists here");
922949
}
950+
// Same per-bundle caveat as the accessibility check: this answers
951+
// for OutLoudDoctor.app, not for the app whose hotkey matters.
952+
if is_separate_doctor_bundle() {
953+
return CheckOutcome::warn(
954+
"this is the DOCTOR's own grant, not the app's: TCC pins \
955+
permissions per bundle and these are different bundles",
956+
ErrorClass::Configuration,
957+
"ask the app itself: dist/OutLoud.app/Contents/MacOS/OutLoud --permissions",
958+
);
959+
}
923960
if hotkey::has_input_monitoring() {
924961
CheckOutcome::pass("hotkey can read key events")
925962
} else {

0 commit comments

Comments
 (0)