.claude/skills/macos-tahoe-fda-headless-binary/SKILL.md
Grant Full Disk Access (FDA) to headless CLI binaries on macOS Tahoe (26.x) when they need to access TCC-protected paths like ~/Library/Messages/chat.db. Use when: (1) A Mach-O binary hangs or returns "authorization denied (code: 23)" accessing ~/Library/Messages, ~/Library/Mail, or similar TCC-protected directories, (2) `tccutil reset SystemPolicyAllFiles` was run and now nothing can access protected paths, (3) The System Settings FDA picker won't show a bare CLI binary after clicking "+", (4) A LaunchAgent-spawned process that previously had FDA stops working after a service restart or npm/brew upgrade, (5) `ls ~/Library/Messages/` returns "Interrupted system call" over SSH. Covers the .app wrapper trick to make bare binaries visible in the FDA picker.
npx skillsauth add Dbochman/dotfiles macos-tahoe-fda-headless-binaryInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
On macOS Tahoe (26.x), CLI binaries (Mach-O executables installed via Homebrew or npm)
that need to read TCC-protected paths like ~/Library/Messages/chat.db require Full Disk
Access (FDA). Unlike GUI apps, these binaries:
+ buttontccutil reset SystemPolicyAllFiles is run~/Library/Messages/chat.db (or similar TCC path) hangs indefinitelyosascript -e 'do shell script "/path/to/binary ..."' returns permissionDenied(path: "...", underlying: authorization denied (code: 23))ls ~/Library/Messages/ returns "Interrupted system call" then hangscp ~/Library/Messages/chat.db /tmp/ fails with "Interrupted system call"sqlite3 ~/Library/Messages/chat.db "SELECT 1" hangs forever+ button in System Settings > Privacy & Security > Full Disk Access lets you
browse to the binary but it doesn't appear in the list after addingmacOS Tahoe's TCC subsystem enforces Full Disk Access at the process level. For
launchd-spawned processes, TCC checks are non-interactive — there's no popup, just a
silent hang (the process blocks on the open() syscall waiting for authorization that
never comes) or an immediate denial.
The System Settings FDA picker is designed for .app bundles. Bare Mach-O executables
(like Homebrew-installed binaries) are technically addable but often fail to appear in the
list due to missing bundle metadata.
On Tahoe, launchd processes get transient TCC sessions. FDA grants may not persist across service restarts because launchd creates a new security session each time. This is different from pre-Tahoe behavior where grants were sticky.
.app WrapperCreate a minimal macOS app bundle that symlinks to the real binary:
BINARY_PATH="/opt/homebrew/Cellar/imsg/0.5.0/libexec/imsg"
BUNDLE_ID="com.steipete.imsg" # Use the binary's actual code-signing identifier
APP_NAME="imsg"
mkdir -p ~/Applications/${APP_NAME}.app/Contents/MacOS
ln -sf "$BINARY_PATH" ~/Applications/${APP_NAME}.app/Contents/MacOS/${APP_NAME}
cat > ~/Applications/${APP_NAME}.app/Contents/Info.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>${BUNDLE_ID}</string>
<key>CFBundleName</key>
<string>${APP_NAME}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME}</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
</dict>
</plist>
EOF
To find the binary's code-signing identifier:
codesign -dv /path/to/binary 2>&1 | grep Identifier
open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'
+~/Applications/ (or /Users/username/Applications/).app wrapper (e.g., imsg.app)If the binary is spawned by a LaunchAgent, the parent process may also need FDA. Add these to the FDA list as well:
OpenClawGateway.app)Terminal.app (for SSH access)Messages.app (if accessing Messages data)launchctl stop ai.openclaw.gateway
launchctl start ai.openclaw.gateway
After granting FDA:
# From SSH - should list files without hanging
ls ~/Library/Messages/
# From osascript (GUI context) - should return data, not "authorization denied"
osascript -e 'do shell script "/opt/homebrew/bin/imsg chats list 2>&1 | head -3"'
# The LaunchAgent process should stay running (no restart loop)
tail -f ~/.openclaw/logs/gateway.log
# Should NOT see: "imsg rpc not ready after Xms" or "auto-restart attempt"
tccutil reset SystemPolicyAllFiles CasuallyThis command nukes ALL Full Disk Access grants for all apps. After running it:
When a binary is updated (e.g., brew upgrade imsg), the FDA grant may be invalidated
if the code signature changes. The .app wrapper uses a symlink, so if the symlink
target path changes (new version directory), you need to:
ln -sf /new/path ~/Applications/imsg.app/Contents/MacOS/imsg| Context | TCC Behavior | |---------|-------------| | GUI (Terminal.app) | TCC prompt appears, user can click Allow | | LaunchAgent (launchd) | No prompt — silent hang or denial | | SSH | No prompt — "Interrupted system call" then hang | | osascript (do shell script) | No prompt — immediate denial with error code 23 |
.app Wrapper TrickThe reason this works is that macOS System Settings' FDA picker specifically looks for
app bundles (directories ending in .app with a valid Info.plist). The CFBundleIdentifier
in the plist should match the binary's code-signing identifier when possible, though
macOS primarily uses the executable path for TCC matching.
imsg binary (by @steipete) is a common case — it's a Swift CLI tool that reads
~/Library/Messages/chat.db via SQLite.OpenClawGateway.app (wrapper) →
node → spawns imsg rpc → reads chat.db. Both imsg.app and OpenClawGateway.app
need FDA.tools
Use exact configured Reolink cameras through the local Home Hub for availability and power status, fresh stills, visual commentary, protected Dylan/Julia/household sharing, and reversible spotlight control. Supports trusted owner tasks and explicitly scoped proactive automations; not for Nest or Ring cameras, arbitrary recipients, recordings, account changes, or raw camera APIs.
data-ai
Privately manage Dylan and Julia's household plant inventory and care history by physical location, bed, and exact Flower Cam view. Use for confirmed plant onboarding from camera conversations, camera- or bed-filtered inventory, record corrections, individual or whole-bed care, and private filtered exports. Pair with reolink-camera when an owner asks about plants visible in Flower Cam images.
testing
Inspect and control the physically secured Reachy Mini at Crosstown through ClawBody. Use for requests to check Reachy, look around, express an emotion, play any official emotion or dance preset, speak proactively, mute or unmute its microphone, stop movement, or describe what its camera sees.
tools
Handle Reachy/iMessage handoffs, selective durable memory, forgetting, and diagnostics; automatic context comes from the gateway plugin.