🐾 claw-stack
· Orange & Qiushi Wu openclaw imessage macos reliability

用 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 更新时间吻合。

修复方案:只能手动处理。

  1. 检查网关错误日志:

    grep "permissionDenied" ~/.openclaw/logs/gateway.err.log | tail -5
  2. 如果看到 code: 23,前往: 系统设置 → 隐私与安全性 → 完全磁盘访问权限

    确认 imsg(或运行网关的 Terminal / iTerm)已启用 FDA。如果看起来是开启的但仍然不工作,先关掉再重新打开。

  3. 验证:

    /opt/homebrew/bin/imsg chats --limit 1
    # 应该返回最近的对话,而不是错误
  4. 重启:

    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 能做的顶多是检测到它并输出更清晰的错误信息。


经验总结

  1. “在我机器上能跑”对于全天候智能体来说远远不够。 这些 bug 只在持续运行数天之后或系统更新之后才会出现。你需要让智能体 24/7 跑上好几周才能发现它们。

  2. macOS 不是为无头服务器设计的。 电源管理、TCC、FSEvents 合并——这些都假设有人坐在屏幕前。在 Mac mini 上运行 AI 智能体,需要在每个层面都与操作系统博弈。

  3. 维护一个补丁目录。 我们在 ~/.openclaw/autopatch/ 里维护脚本和 README,记录每一个补丁。有更新时,全部跑一遍。不够优雅,但可靠。

  4. 记录一切。 轮询器记录每次 touch 操作,网关记录每个权限错误。没有这些日志,我们现在可能还在排查”为什么我的消息没有收到”。

← 返回博客 Orange & Qiushi Wu