Summary
imsg fails to open ~/Library/Messages/chat.db on macOS 26.3.1 (Tahoe) with authorization denied (code: 23). This is caused by a new macOS TCC restriction that blocks SQLite WAL locking on chat.db for non-system processes, even with Full Disk Access granted.
The fix is to open chat.db in read-only immutable mode (?mode=ro&immutable=1).
Environment
- macOS 26.3.1 (Darwin 25.3.0, arm64, Apple M4 Pro)
- imsg 0.5.0 (Homebrew,
steipete/tap/imsg)
- SQLite 3.51.0
- Full Disk Access: granted for
imsg binary AND parent process (node)
- Automation > Messages: granted
Reproduction
# imsg fails
$ imsg chats
permissionDenied(path: "/Users/.../Library/Messages/chat.db", underlying: authorization denied (code: 23))
# Direct sqlite3 also fails (normal mode tries to acquire WAL lock)
$ sqlite3 ~/Library/Messages/chat.db "SELECT COUNT(*) FROM message;"
Error: unable to open database "...": authorization denied
# BUT: file read works fine (FDA is active)
$ file ~/Library/Messages/chat.db
SQLite 3.x database, last written using SQLite version 3051000...
$ wc -c ~/Library/Messages/chat.db
1527808
# AND: sqlite3 read-only immutable mode works perfectly
$ sqlite3 "file:$HOME/Library/Messages/chat.db?mode=ro&immutable=1" "SELECT COUNT(*) FROM message;"
344
# AND: copying the file then opening the copy works
$ cp ~/Library/Messages/chat.db /tmp/chat-copy.db
$ sqlite3 /tmp/chat-copy.db "SELECT COUNT(*) FROM message;"
344
Root cause analysis
macOS 26.3.x introduced stricter protection on ~/Library/Messages/chat.db. The change does not block file reads (cp, cat, file all work) but blocks SQLite WAL locking operations that sqlite3 performs when opening a database in normal mode.
When SQLite opens a database, it attempts to acquire a shared lock for WAL (Write-Ahead Logging) mode. macOS now intercepts this lock attempt on chat.db and denies it for non-Apple processes, even with Full Disk Access enabled.
The evidence:
ls, file, wc, head, cp all work → FDA is effective, file-level read is allowed
sqlite3 chat.db fails → lock acquisition is blocked
sqlite3 "file:chat.db?mode=ro&immutable=1" works → bypasses locking entirely
sqlite3 /tmp/copy.db works → the data is not corrupted, only in-place locking is blocked
Suggested fix
Open chat.db with the SQLite URI flag immutable=1 (or at minimum mode=ro). This tells SQLite to skip all locking, which is safe for read-only access.
In Swift (GRDB/SQLite.swift):
// Before (broken on macOS 26.3+)
let db = try DatabaseQueue(path: chatDbPath)
// After
let db = try DatabaseQueue(path: chatDbPath, configuration: Configuration { config in
config.readonly = true
})
// Or via URI:
let db = try DatabaseQueue(path: "file:\(chatDbPath)?mode=ro&immutable=1")
In raw C SQLite API:
// Before
sqlite3_open(path, &db);
// After
sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL);
// Or:
sqlite3_open_v2("file:path?immutable=1", &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL);
Since imsg only reads from chat.db (never writes), immutable=1 is the correct flag.
Impact
This breaks imsg completely on macOS 26.3+ — no commands that read Messages work. The binary was installed via Homebrew on 2026-03-17 (before the macOS update), and the issue appeared after updating to macOS 26.3.1.
This likely affects all users who upgrade to macOS 26.3+.
Investigated with Claude Code (Opus 4.6). All reproduction steps verified on a Mac mini M4 Pro running macOS 26.3.1.
Summary
imsgfails to open~/Library/Messages/chat.dbon macOS 26.3.1 (Tahoe) withauthorization denied (code: 23). This is caused by a new macOS TCC restriction that blocks SQLite WAL locking onchat.dbfor non-system processes, even with Full Disk Access granted.The fix is to open
chat.dbin read-only immutable mode (?mode=ro&immutable=1).Environment
steipete/tap/imsg)imsgbinary AND parent process (node)Reproduction
Root cause analysis
macOS 26.3.x introduced stricter protection on
~/Library/Messages/chat.db. The change does not block file reads (cp, cat, file all work) but blocks SQLite WAL locking operations that sqlite3 performs when opening a database in normal mode.When SQLite opens a database, it attempts to acquire a shared lock for WAL (Write-Ahead Logging) mode. macOS now intercepts this lock attempt on
chat.dband denies it for non-Apple processes, even with Full Disk Access enabled.The evidence:
ls,file,wc,head,cpall work → FDA is effective, file-level read is allowedsqlite3 chat.dbfails → lock acquisition is blockedsqlite3 "file:chat.db?mode=ro&immutable=1"works → bypasses locking entirelysqlite3 /tmp/copy.dbworks → the data is not corrupted, only in-place locking is blockedSuggested fix
Open
chat.dbwith the SQLite URI flagimmutable=1(or at minimummode=ro). This tells SQLite to skip all locking, which is safe for read-only access.In Swift (GRDB/SQLite.swift):
In raw C SQLite API:
Since
imsgonly reads fromchat.db(never writes),immutable=1is the correct flag.Impact
This breaks
imsgcompletely on macOS 26.3+ — no commands that read Messages work. The binary was installed via Homebrew on 2026-03-17 (before the macOS update), and the issue appeared after updating to macOS 26.3.1.This likely affects all users who upgrade to macOS 26.3+.
Investigated with Claude Code (Opus 4.6). All reproduction steps verified on a Mac mini M4 Pro running macOS 26.3.1.