feat: add macOS permission diagnostics and guidance - #21
Merged
Conversation
ruby-dlee
force-pushed
the
fm/macos-permissions-doc-p4
branch
from
July 22, 2026 18:40
d818208 to
8249a59
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Document the exact macOS TCC permission grants required by the Firstmate toolchain so an operator can set up or repair a machine reliably after silent stalls caused by missing permissions. Add only docs/macos-permissions.md, bin/fm-macos-permissions.sh, and a colocated test: map Ghostty, Claude Code, Codex, no-mistakes, and its daemon to only the Full Disk Access, Automation controller-to-target relationships, Screen Recording, and Accessibility grants they genuinely need based on how Firstmate uses them; explain each capability and mark every grant that requires the captain to click in System Settings. The helper must safely and honestly report only detectable status, expose chicken-and-egg and genuinely undetectable cases as unknown rather than guessing, offer the verified macOS 26 Privacy pane deep links, remain idempotent and read-only except for opening settings panes, never invoke tccutil or fake, force, reset, or bypass TCC, and be shellcheck-clean. Preserve one sentence per line in the document, make no unrelated edits, do not touch AGENTS.md, and do not add an agent co-author.
What Changed
Risk Assessment
✅ Low: The change is limited to the three authorized files, consistently treats non-authoritative evidence as unknown, and conditions permission guidance on macOS-observed service identities and controller-target pairs.
Testing
The successful full baseline was supplemented with the focused permission-helper suite, a live read-only macOS report, and visual confirmation that all four macOS 26 deep links opened the correct privacy panes; everything passed, no grants changed, and the worktree was clean afterward.
Evidence: Live read-only operator report
/var/folders/y_/bfdbj_vx20l9b9tw7crgkzwm0000gn/T/no-mistakes-evidence/01KY58TX2ZDMCWQ80YBY7DHKH7/full-disk-access-pane.png) - Evidence: Automation pane (local file:/var/folders/y_/bfdbj_vx20l9b9tw7crgkzwm0000gn/T/no-mistakes-evidence/01KY58TX2ZDMCWQ80YBY7DHKH7/automation-pane.png) - Evidence: Screen & System Audio Recording pane (local file:/var/folders/y_/bfdbj_vx20l9b9tw7crgkzwm0000gn/T/no-mistakes-evidence/01KY58TX2ZDMCWQ80YBY7DHKH7/screen-recording-pane.png) - Evidence: Accessibility pane (local file:/var/folders/y_/bfdbj_vx20l9b9tw7crgkzwm0000gn/T/no-mistakes-evidence/01KY58TX2ZDMCWQ80YBY7DHKH7/accessibility-pane.png)Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 4 issues found → auto-fixed (6) ✅
bin/fm-macos-permissions.sh:98- This contradicts the required criterion to “safely and honestly report only detectable status.”stored_statuscombines distinct client identities and both TCC databases, then reportsGRANTEDif any row is allowed—even if the responsible identity is denied, another database query failed, or an alternate/stale identity was granted. Report each identity separately or returnUNKNOWNfor mixed or incomplete evidence.bin/fm-macos-permissions.sh:303- This contradicts the requirement to expose genuinely undetectable cases as unknown.has_apple_events_entitlementcan returnUNKNOWN, but this two-way branch treats everything exceptMISSINGas present and printsPER TARGET; additionally, line 196 converts a failedcodesigninspection intoMISSING. PreserveUNKNOWNthrough both paths.docs/macos-permissions.md:5- The required criterion says to mark grants that require a captain click in System Settings, but this claims every Automation grant requires that flow. First-time Automation authorization normally uses an Allow dialog; System Settings reviews or changes the resulting relationship, as documented in Apple’s guide. Distinguish the prompt flow from permissions that must be enabled in System Settings; lines 42, 56, 68, 84, and 119 repeat the ambiguity.tests/fm-macos-permissions.test.sh:30- Thesqlite3fake accepts every argument, so removing production’s-readonlyflag would leave this safety-critical test green. Make the fake reject or record invocations lacking-readonly.🔧 Fix: Captain: preserve unknown macOS permission states
4 errors still open:
docs/macos-permissions.md:5- This conflicts with the required exact macOS 26 click map: the document says every Screen & System Audio Recording grant requires System Settings, reinforced at lines 48, 76, 94, and 128, but Apple’s macOS 26 guide says first-use access can be authorized with Allow or Allow While Using the App in the dialog. Distinguish first-use prompts from denied or manually added entries that require System Settings.bin/fm-macos-permissions.sh:154- This contradicts the requirement to “safely and honestly report only detectable status.”query_failedis considered only when no database returned rows, so one successful query plus one failed query still prints conclusive GRANTED/MISSING Automation relationships from partial evidence. Report UNKNOWN or explicitly mark the returned rows incomplete whenever any expected query fails.bin/fm-macos-permissions.sh:214- This contradicts the required unknown-rather-than-guessing boundary. The probe treats any occurrence of the entitlement key as PRESENT—including a Boolean false value—then treats every absent key as proof that Automation is blocked without checking runtime or target-signing conditions. Apple defines this as a Boolean and documents a same-team exception. Parse the Boolean value and avoidBLOCKEDunless the relevant prerequisites are established.bin/fm-macos-permissions.sh:238- This contradicts the requirement to report only detectable status. The output labels__CFBundleIdentifiermerely a launch hint, but this inheritable, user-controlled environment value authoritatively replaces Ghostty’s stored Full Disk Access result with the current-process probe. A stale or overridden value can therefore attribute GRANTED or MISSING to Ghostty from another context; keep the probe separate unless responsibility is verified, or return UNKNOWN when evidence conflicts.🔧 Fix: Captain: correct macOS permission evidence and approval flows
3 errors still open:
bin/fm-macos-permissions.sh:235- This contradicts the required exact/reliable mapping and unknown-rather-than-guessing boundary. The “no-mistakes daemon” executable is derived solely from the interactive caller’sPATH, then used for TCC queries, entitlement inspection, and captain instructions. Launchd may be running another configured or older executable, causing diagnosis and grants to target the wrong binary. Resolve the launch-agent/running daemon identity read-only and reportUNKNOWNwhen it cannot be tied to this path.bin/fm-macos-permissions.sh:106- This conflicts with the requirement to report only detectable status. Anauth_value=2database row is labeledGRANTEDwithout checking whether its stored code-signing requirement still matches the installed executable; the comment at lines 76–78 acknowledges that replacements can invalidate this evidence. The global “advisory” note does not prevent Screen Recording and Accessibility from presenting a stale allow as an effective grant. Verify the stored requirement or reportSTORED ALLOW/UNKNOWN.bin/fm-macos-permissions.sh:163- This conflicts with the required unknown-rather-than-guessing boundary. Duplicate records for one Automation controller-target pair with different decisions are printed as simultaneousGRANTEDandMISSINGlines without database or client-type provenance, leaving the effective relationship undetectable but not markedUNKNOWN. Collapse each displayed pair only when all records agree, or retain provenance and explicitly mark conflicts unknown.🔧 Fix: Captain: default macOS permissions to authoritative evidence
3 errors still open:
bin/fm-macos-permissions.sh:330- This violates the governing rule that stale evidence must remainUNKNOWN.launchctlidentifies a running job and configured path, butcodesigninspects the path’s current filesystem target rather than the executable image already loaded by the captured PID. After an in-place replacement or symlink retarget, lines 399–406 can reportENTITLEMENT NOT PRESENTorPER TARGETfor a different binary. Tie the inspected file to the live image or keep active-daemon capabilityUNKNOWN.bin/fm-macos-permissions.sh:379- This contradicts the requirement to report only detectable status. The helper unconditionally says direct Codex has the Apple Events entitlement without inspecting the resolved Codex binary, so absence, installation variants, or upgrades can make the claim false. Dynamically inspect an authoritative identity or report the capability asUNKNOWN; the same unversioned claim appears indocs/macos-permissions.mdline 77.bin/fm-macos-permissions.sh:364- This violates the explicit rule that partial evidence must remainUNKNOWN. An emptycommand -vresult is rendered asnot installedfor Claude, Codex, and the no-mistakes CLI, but it proves only that the command was not found on the currentPATH. ReportUNKNOWN (not found on PATH)rather than asserting installation state.🔧 Fix: Captain: remove stale capability and installation claims
1 error still open:
docs/macos-permissions.md:104- This contradicts the required mapping to “only ... grants they genuinely need” and the governing rule to assert only from authoritative current evidence. Line 101 establishes only a dated ScreenCaptureKit attribution, but this line instructs the captain to grant Accessibility to “the same responsible daemon binary”; the helper likewise asserts Screen Recording and Accessibility attribution at lines 412–414. Report these identities as unknown and direct the operator to the exact responsible entry observed for each service.🔧 Fix: Captain: require service-specific daemon permission attribution
2 errors still open:
docs/macos-permissions.md:44- This contradicts the requirement to map “only the ... grants they genuinely need” using authoritative evidence. It instructs the captain to add Ghostty whenever a terminal-launched agent needs protected data, although line 33 says command ancestry cannot establish the responsible identity. Require macOS/TCC to attribute this service to Ghostty before recommending the grant.docs/macos-permissions.md:63- This contradicts the required exact Automation controller-to-target mapping. “The relationship is Claude Code controlling...” asserts the TCC controller identity even though line 33 says ancestry cannot establish responsibility; direct the operator to the exact controller-to-target pair macOS observes, as the Codex section already does.🔧 Fix: Captain: require observed macOS permission attribution
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; uv run --directory tools/agent-fleet --locked pytest || rc=1; uv run --directory tools/agent-fleet --locked python -m compileall -q src || rc=1; exit "$rc"Configured full shell and Python baseline command supplied by the gate (reported successful).bash tests/fm-macos-permissions.test.shbin/fm-macos-permissions.shwith its live read-only report captured as evidence.bin/fm-macos-permissions.sh --open full-disk-accesswith visual System Settings verification.bin/fm-macos-permissions.sh --open automationwith visual System Settings verification.bin/fm-macos-permissions.sh --open screen-recordingwith visual System Settings verification.bin/fm-macos-permissions.sh --open accessibilitywith visual System Settings verification.git diff --summary 609cf8b1474f6350e0292443492ac3e03594bce8..8347dd613fd06ca46717bebdb0e219e8e4a2fee4and commit-message inspection for scope, modes, and co-author metadata.git status --shortafter removing the generatedbin/__pycache__test artifact.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.