imsg is local-only, but Messages.app data sits behind macOS privacy gates. Three permissions cover every feature; only the first is mandatory.
#Full Disk Access — required
imsg reads ~/Library/Messages/chat.db directly. macOS denies that path to every process that hasn't been added to Full Disk Access.
Grant it under System Settings → Privacy & Security → Full Disk Access.
You almost always need to add at least two entries:
- The terminal app you'll launch
imsgfrom (Terminal.app, iTerm2, Ghostty, WezTerm, Alacritty, …). - The built-in Terminal at
/System/Applications/Utilities/Terminal.app. macOS sometimes consults this default grant even when you're using a different terminal.
If imsg is launched indirectly — by an editor's task runner, a Node script, an SSH session, an automation gateway — the parent process needs the grant, not the terminal you opened. Add that parent app too.
After changing entries, quit and relaunch the parent process. macOS only re-reads Full Disk Access on launch.
imsg opens chat.db read-only. It does not pass SQLite's immutable=1 flag because immutable handles can miss WAL-backed updates that Messages writes during normal use.
#Automation — required for sends and tapbacks
imsg send, imsg react, imsg typing, and imsg read drive Messages.app via AppleScript. macOS gates that under Automation.
The first time you run a send, macOS prompts:
"Terminal" wants to control "Messages".
Approve it, or pre-approve under System Settings → Privacy & Security → Automation → Messages. Toggle the terminal (or wrapper app) on.
If you previously denied the prompt, the toggle will appear here and you can re-enable it without re-prompting.
#Contacts — optional
When granted, imsg resolves names from your Address Book and includes them as contact_name / display_name / sender_name in JSON output. Raw handle and sender values are always preserved, so automation that keys on phone numbers or email addresses is unaffected.
Grant it under System Settings → Privacy & Security → Contacts.
If you skip this, JSON output simply leaves the resolved name fields empty. Nothing else changes.
#Why these grants live in three different places
macOS treats each gate as a separate consent decision:
| Gate | What it protects | Triggered by |
|---|---|---|
| Full Disk Access | ~/Library/Messages/, Mail, Safari history, … | imsg chats, history, watch, group, anything that opens chat.db. |
| Automation | One app driving another via Apple Events | imsg send, react, read, typing. |
| Contacts | Address Book entries | Name resolution in any read or send command. |
Only Full Disk Access is mandatory. Skip Automation if you don't send. Skip Contacts if you don't need name resolution. The CLI degrades cleanly — it tells you which gate is missing instead of silently failing.
#Stale grants after updates
After Homebrew, terminal, or macOS updates, Full Disk Access entries can go stale. The symptom is unable to open database file or empty output even though the entry looks toggled on.
Fix it by toggling the entry off, then on again. macOS regenerates the underlying TCC record. Do the same after replacing the parent app (e.g. updating Ghostty).
See Troubleshooting for the full diagnosis loop.