Skip to content

feat: add macOS permission diagnostics and guidance - #21

Merged
ruby-dlee merged 7 commits into
mainfrom
fm/macos-permissions-doc-p4
Jul 22, 2026
Merged

feat: add macOS permission diagnostics and guidance#21
ruby-dlee merged 7 commits into
mainfrom
fm/macos-permissions-doc-p4

Conversation

@ruby-dlee

Copy link
Copy Markdown
Owner

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

  • Add a conservative, read-only macOS TCC helper that reports detectable permission, Automation, entitlement, and daemon identity evidence, and opens the relevant Privacy & Security panes.
  • Document observed Full Disk Access, Automation, Screen Recording, and Accessibility requirements for Ghostty, Claude Code, Codex, no-mistakes, and its daemon.
  • Add coverage for unknown or conflicting evidence, read-only database access, daemon resolution, pane links, mutation prevention, and non-macOS refusal, and index the new guide and helper.

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
$ bin/fm-macos-permissions.sh
firstmate macOS permission report
  unverified bundle environment hint: not exposed by this process (not used for attribution)
  current invocation protected-path probe: DENIED
  readable TCC databases: 0 (at least one expected database is unreadable)
  Stored permission statuses therefore remain UNKNOWN unless behavior proves otherwise.
  Automation status is always PER TARGET because macOS grants one controller-to-app relationship at a time.

Ghostty (terminal launcher)
  Full Disk Access                requirement=CONDITIONAL               status=UNKNOWN
    Protected-path work needs the exact responsible entry macOS observes; add Ghostty only when macOS names it.
  Automation                      requirement=CONDITIONAL               status=PER TARGET
    The first-use Allow dialog establishes the exact controller-to-target pair for terminal-driven app automation; tmux does not use Apple Events.
  Screen Recording                requirement=CONDITIONAL               status=UNKNOWN
    Native desktop capture needs the exact responsible entry macOS observes; add Ghostty only when macOS names it.
  Accessibility                   requirement=CONDITIONAL               status=UNKNOWN
    Native UI control needs the exact responsible entry macOS observes; add Ghostty only when macOS names it.

Claude Code PATH command target (/Users/dongkeun/.local/share/claude/versions/2.1.217)
  Full Disk Access                requirement=LAUNCHER OR CONDITIONAL   status=UNKNOWN
    Use the exact responsible entry macOS observes for protected-path access; command ancestry alone does not establish it.
  Automation                      requirement=CONDITIONAL               status=PER TARGET
    The first-use Allow dialog establishes the exact controller-to-target pair for app automation; approve Claude only when macOS names it as controller.
  Screen Recording                requirement=CONDITIONAL               status=UNKNOWN
    Native desktop capture needs the exact responsible entry macOS observes; command ancestry does not establish it.
  Accessibility                   requirement=CONDITIONAL               status=UNKNOWN
    Native UI control needs the exact responsible entry macOS observes; command ancestry does not establish it.

Codex PATH command target (/Users/dongkeun/.codex/plugins/.plugin-appserver/codex)
  Full Disk Access                requirement=LAUNCHER OR CONDITIONAL   status=UNKNOWN
    Use the exact responsible entry macOS observes for protected-path access; the current PATH command does not establish it.
  Automation                      requirement=CONDITIONAL               status=UNKNOWN
    codesign reports the Apple Events entitlement as true for the current PATH command filesystem target; the active controller identity remains unknown.
  Screen Recording                requirement=REQUIRED FOR COMPUTER USE status=UNKNOWN
    Native Computer Use needs screen pixels for the exact responsible entry macOS observes; DevTools screenshots do not.
  Accessibility                   requirement=REQUIRED FOR COMPUTER USE status=UNKNOWN
    Native Computer Use needs Accessibility for the exact responsible entry macOS observes.

no-mistakes CLI PATH entry (/Users/dongkeun/.local/bin/no-mistakes)
  All four permissions            requirement=NOT NEEDED BY CLI CORE    status=N/A
    The CLI coordinates with the daemon; child-agent capabilities belong to the exact service-specific entry macOS observes.

no-mistakes daemon configured target (/Users/dongkeun/.no-mistakes/bin/no-mistakes)
  authoritative launch job: com.kunchenguid.no-mistakes.daemon.19e4cf00
  Full Disk Access                requirement=CONDITIONAL               status=UNKNOWN
    A daemon-launched agent may need it for protected paths, but the responsible identity is unknown until macOS identifies it for this service.
  Automation                      requirement=CONDITIONAL               status=UNKNOWN
    The configured path cannot prove the running process image entitlement, so target-specific capability remains unknown.
  Screen Recording                requirement=REQUIRED FOR COMPUTER USE status=UNKNOWN
    Daemon-launched Computer Use needs capture, but the responsible identity is unknown; use the exact Screen Recording entry macOS observes.
  Accessibility                   requirement=REQUIRED FOR COMPUTER USE status=UNKNOWN
    Daemon-launched Computer Use needs UI control, but the responsible identity is unknown; use the exact Accessibility entry macOS observes.

Stored Automation candidate relationships (not proof of responsibility)
  Ghostty:
    UNKNOWN (not all expected TCC databases are readable in this context)
  Claude Code:
    UNKNOWN (not all expected TCC databases are readable in this context)
  Codex:
    UNKNOWN (not all expected TCC databases are readable in this context)
  no-mistakes daemon:
    UNKNOWN (not all expected TCC databases are readable in this context)

No grant was changed.
Full Disk Access and Accessibility require a human click in System Settings.
Automation and Screen Recording may first ask for approval in a dialog.
Use System Settings to review or change recorded access, or to add Screen Recording access manually.
Open one pane with --open full-disk-access|automation|screen-recording|accessibility.
- Evidence: Full Disk Access pane (local file: /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_status combines distinct client identities and both TCC databases, then reports GRANTED if 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 return UNKNOWN for mixed or incomplete evidence.
  • 🚨 bin/fm-macos-permissions.sh:303 - This contradicts the requirement to expose genuinely undetectable cases as unknown. has_apple_events_entitlement can return UNKNOWN, but this two-way branch treats everything except MISSING as present and prints PER TARGET; additionally, line 196 converts a failed codesign inspection into MISSING. Preserve UNKNOWN through 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 - The sqlite3 fake accepts every argument, so removing production’s -readonly flag 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_failed is 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 avoid BLOCKED unless the relevant prerequisites are established.
  • 🚨 bin/fm-macos-permissions.sh:238 - This contradicts the requirement to report only detectable status. The output labels __CFBundleIdentifier merely 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’s PATH, 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 report UNKNOWN when it cannot be tied to this path.
  • 🚨 bin/fm-macos-permissions.sh:106 - This conflicts with the requirement to report only detectable status. An auth_value=2 database row is labeled GRANTED without 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 report STORED 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 simultaneous GRANTED and MISSING lines without database or client-type provenance, leaving the effective relationship undetectable but not marked UNKNOWN. 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 remain UNKNOWN. launchctl identifies a running job and configured path, but codesign inspects 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 report ENTITLEMENT NOT PRESENT or PER TARGET for a different binary. Tie the inspected file to the live image or keep active-daemon capability UNKNOWN.
  • 🚨 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 as UNKNOWN; the same unversioned claim appears in docs/macos-permissions.md line 77.
  • 🚨 bin/fm-macos-permissions.sh:364 - This violates the explicit rule that partial evidence must remain UNKNOWN. An empty command -v result is rendered as not installed for Claude, Codex, and the no-mistakes CLI, but it proves only that the command was not found on the current PATH. Report UNKNOWN (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.sh
  • bin/fm-macos-permissions.sh with its live read-only report captured as evidence.
  • bin/fm-macos-permissions.sh --open full-disk-access with visual System Settings verification.
  • bin/fm-macos-permissions.sh --open automation with visual System Settings verification.
  • bin/fm-macos-permissions.sh --open screen-recording with visual System Settings verification.
  • bin/fm-macos-permissions.sh --open accessibility with visual System Settings verification.
  • git diff --summary 609cf8b1474f6350e0292443492ac3e03594bce8..8347dd613fd06ca46717bebdb0e219e8e4a2fee4 and commit-message inspection for scope, modes, and co-author metadata.
  • git status --short after removing the generated bin/__pycache__ test artifact.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@ruby-dlee
ruby-dlee force-pushed the fm/macos-permissions-doc-p4 branch from d818208 to 8249a59 Compare July 22, 2026 18:40
@ruby-dlee
ruby-dlee merged commit 44b764e into main Jul 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant