用 OpenClaw 让 iMessage 稳定运行:3 个问题与修复方案
在 Mac mini 上全天候运行 OpenClaw 的 iMessage 通道,暴露了三个可靠性问题——FSEvents 合并、附件路径沙箱限制、以及 TCC 权限被静默重置。以下是问题根因与修复方式。
OpenClaw 可以把 iMessage 当作通信渠道——你发短信给 AI 智能体,它回复你。听起来很简单,但在 Mac mini 上 24/7 运行之后,我们发现了三个可靠性问题,花了好几周才完全定位。下面是每个问题的经过和修复方案。
架构概述
OpenClaw 的 iMessage 插件通过 FSEvents 文件系统事件监听 ~/Library/Messages/chat.db。新消息到达时,macOS 写入 chat.db,监听器检测到变化,网关处理消息。
理论上是即时的。实践中,它会以三种不同方式出问题。
问题一:空闲时消息延迟最长 5 分钟
现象:你发了一条消息,手机显示”已送达”,但智能体 3-5 分钟都没有响应,然后突然一次性处理完所有消息。
根本原因:macOS 电源管理会对后台进程的 FSEvents 进行合并。即使 LaunchAgent plist 里设置了 ProcessType=Interactive,并且运行了 caffeinate,内核在低活跃期间仍然会批量处理 chat.db 的 vnode 事件。imsg rpc 子进程在监听文件,但 macOS 判断”这个进程最近不活跃,把文件通知批量处理一下”。
为什么难以排查:消息其实已经写入 chat.db 了——被延迟的是通知,不是消息本身。所以活跃使用时一切正常,机器空闲时则静默失效。
修复方案:一个轮询脚本,每 15 秒检查一次 chat.db,发现新行时 touch 该文件,触发一个新的 FSEvent:
#!/usr/bin/env node
// imsg-poller.mjs — Polls chat.db for new messages and wakes FSEvents watcher
import { execSync } from 'child_process';
import { utimesSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
const CHATDB = join(homedir(), 'Library/Messages/chat.db');
const INTERVAL = 15000; // 15 seconds
function getMaxRowid() {
try {
return execSync(
`/usr/bin/sqlite3 "${CHATDB}" "SELECT MAX(ROWID) FROM message;"`,
{ timeout: 5000, encoding: 'utf8' }
).trim() || '0';
} catch { return '0'; }
}
let lastRowid = getMaxRowid();
if (lastRowid === '0') {
console.error('ERROR: Cannot read chat.db — check Full Disk Access');
process.exit(1);
}
console.log(`imsg-poller started. ROWID: ${lastRowid}, interval: ${INTERVAL}ms`);
setInterval(() => {
const current = getMaxRowid();
if (current !== '0' && current !== lastRowid) {
console.log(`New message (ROWID ${lastRowid} -> ${current}), touching chat.db`);
try {
const now = new Date();
utimesSync(CHATDB, now, now);
} catch (e) {
console.error(`touch failed: ${e.message}`);
}
lastRowid = current;
}
}, INTERVAL);
为什么用 Node.js 而不用 bash? 我们先试了 bash 版本,但 launchd 启动的 /bin/bash 进程不继承完全磁盘访问权限(TCC)。stat 命令能正常工作,但 sqlite3 会报”授权被拒绝”。使用 /opt/homebrew/bin/node 可以,因为它继承了与网关相同的 TCC 授权下的 FDA。
部署方式:以 KeepAlive: true 的 LaunchAgent 运行:
<?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>Label</key>
<string>ai.openclaw.imsg-poller</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/path/to/imsg-poller.mjs</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>/Users/youruser</string>
</dict>
<key>ThrottleInterval</key><integer>10</integer>
</dict>
</plist>
OpenClaw 更新后还有效吗? 有效——它是一个独立的 launchd 任务。
问题二:通过 iMessage 发送图片时报”路径不允许”
现象:智能体尝试发送一张通过 iMessage 收到的图片,但报错”本地媒体路径不在允许的目录下”。图片确实存在于 ~/Library/Messages/Attachments/...,但 OpenClaw 的媒体沙箱拦截了它。
根本原因:OpenClaw 的 buildMediaLocalRoots() 函数定义了允许访问的媒体目录,包含工作区、临时目录和沙箱——但不包含 ~/Library/Messages/Attachments/。当智能体尝试转发或处理通过 iMessage 收到的图片时,路径被拒绝。
修复方案:一个补丁脚本,将 Messages 附件目录添加到允许的根目录中:
#!/usr/bin/env bash
# patch-imessage-attachments.sh
# Adds ~/Library/Messages/Attachments to allowed media roots
# Re-run after every `npm update -g openclaw`
DIST="/opt/homebrew/lib/node_modules/openclaw/dist"
patched=0
for f in "$DIST"/ir-*.js; do
[ -f "$f" ] || continue
if grep -q "buildMediaLocalRoots" "$f" && \
! grep -q "Messages/Attachments" "$f"; then
sed -i '' 's|path.join(resolvedStateDir, "sandboxes")|path.join(resolvedStateDir, "sandboxes"),\n\t\tpath.join(os.homedir(), "Library/Messages/Attachments")|' "$f"
echo "Patched: $(basename $f)"
patched=$((patched + 1))
fi
done
echo "Done. Patched: $patched files"
echo "Run: openclaw gateway restart"
OpenClaw 更新后还有效吗? 不——编译后的 JS 文件会被覆盖。每次更新后必须重新运行此脚本。
问题三:macOS 更新后静默撤销完全磁盘访问权限
现象:iMessage 完全停止工作。没有新消息被接收,网关日志里没有明显的错误。智能体看起来在线,但实际上什么都听不到。
根本原因:macOS 系统更新(有时是小型安全补丁)可能重置 TCC(透明度、许可和控制)权限。发生这种情况时,imsg 二进制失去完全磁盘访问权限,无法读取 ~/Library/Messages/chat.db。网关日志显示:
permissionDenied(path: "~/Library/Messages/chat.db",
underlying: authorization denied (code: 23))
在我们的日志中,这种情况发生在 2026 年 2 月 13 日和 2 月 24 日——两次都与 macOS 更新时间吻合。
修复方案:只能手动处理。
-
检查网关错误日志:
grep "permissionDenied" ~/.openclaw/logs/gateway.err.log | tail -5 -
如果看到
code: 23,前往: 系统设置 → 隐私与安全性 → 完全磁盘访问权限确认
imsg(或运行网关的 Terminal / iTerm)已启用 FDA。如果看起来是开启的但仍然不工作,先关掉再重新打开。 -
验证:
/opt/homebrew/bin/imsg chats --limit 1 # 应该返回最近的对话,而不是错误 -
重启:
openclaw gateway restart
OpenClaw 更新后还有效吗? 有效——TCC 权限是系统级别的。但 macOS 更新可能重置它们。
更新后检查清单
每次运行 npm update -g openclaw 后,执行以下操作:
# 1. 重新应用补丁(被更新覆盖了)
bash ~/.openclaw/autopatch/patch-imessage-attachments.sh
# 2. 重启网关
openclaw gateway restart
# 3. 验证 iMessage 正常工作
/opt/homebrew/bin/imsg chats --limit 1
macOS 更新后,还要检查完全磁盘访问权限。
这些问题应该由 OpenClaw 上游修复吗?
问题一(FSEvents 合并)是 macOS 内核行为——OpenClaw 本身很难修复。轮询方案是正确的临时解法,OpenClaw 可以考虑将其作为可选组件提供。
问题二(附件路径)是明显的 bug/疏漏。当 iMessage 插件启用时,~/Library/Messages/Attachments/ 应该默认在允许的根目录中。这是上游的一行代码修复。
问题三(TCC 重置)是 Apple 的问题。OpenClaw 能做的顶多是检测到它并输出更清晰的错误信息。
经验总结
-
“在我机器上能跑”对于全天候智能体来说远远不够。 这些 bug 只在持续运行数天之后或系统更新之后才会出现。你需要让智能体 24/7 跑上好几周才能发现它们。
-
macOS 不是为无头服务器设计的。 电源管理、TCC、FSEvents 合并——这些都假设有人坐在屏幕前。在 Mac mini 上运行 AI 智能体,需要在每个层面都与操作系统博弈。
-
维护一个补丁目录。 我们在
~/.openclaw/autopatch/里维护脚本和 README,记录每一个补丁。有更新时,全部跑一遍。不够优雅,但可靠。 -
记录一切。 轮询器记录每次
touch操作,网关记录每个权限错误。没有这些日志,我们现在可能还在排查”为什么我的消息没有收到”。