.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.development
Search the web for current information, news, facts, and answers. Use when asked questions about current events, needing to look something up, finding websites, researching topics, or when you need up-to-date information beyond your training data.
development
Summarize any URL, YouTube video, podcast, PDF, or file into concise text. Use when asked to read an article, summarize a link, get the gist of a video or podcast, extract content from a URL, or when you need to understand what a web page or document contains.
development
Play music via Spotify and control Google Home speakers. Use when asked to play music, songs, artists, playlists, podcasts, or control speakers/volume/audio.
testing
Create new OpenClaw skills, modify and improve existing skills, and measure skill performance with evals. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Also use when asked to "make a skill", "turn this into a skill", "improve this skill", or "test this skill".