1. openclaw 驱动 iMessage 机器人时,为什么 permission 会混进 JSON 解析链路
在 macOS 上跑 openclaw 的 iMessage 通道,最容易让人误判的一类崩溃就是终端里刷出Unexpected token 'p', "permission"... is not valid JSON。表面看是 JSON 解析器挂了,实际上解析器只是最后一个受害者。openclaw 的 gateway 在启动 iMessage 通道时,会通过一个本地 RPC 子进程去读/Users/<你>/Library/Messages/chat.db,这个文件属于 macOS 隐私保护范围,没拿到完全磁盘访问权限时,系统直接返回一段纯文本拒绝信息,而 RPC 层默认把回包当 JSON 解析,于是第一个字符p就把解析器打崩,进程退出码 1,随后进入 auto-restart 循环。
这个场景适合谁:正在用 openclaw 搭 iMessage 机器人、或者用任何需要读 chat.db 的本地网关工具,并且已经准备把模型调用统一走 TaoToken 的开发者。你需要同时搞定两件事——macOS 的 TCC 权限,以及 openclaw 的 config.toml / settings.json 骨架里模型通道的配置。前者决定 chat.db 能不能读,后者决定机器人拿到消息后能不能稳定调用模型。两件事混在一起排查时,日志会互相干扰,所以下面按“先权限、后配置、再验证”的顺序拆开。
我试过把权限和模型配置分开验证,效率比一起改高很多。核心检索词先记住三个:openclaw、iMessage、permission 不是合法 JSON。只要这三个词同时出现,基本可以锁定是 TCC 拦截 + 上层误判 JSON 的级联故障,而不是你的 JSON 文件写错了。
2. TaoToken 前置:统一 Key 与 API 通道,让 openclaw 的模型调用不再散落
openclaw 的 iMessage 机器人通常有两段外部依赖:一段是读本地 chat.db 的 RPC,一段是调用大模型的 HTTP 请求。前者是 macOS 权限问题,后者是配置问题。把模型调用收敛到 TaoToken 的统一通道,好处是 Key 只有一份、base_url 只有一个、出问题时排查面小。TaoToken 提供 OpenAI 兼容的接口形态,openclaw 这类工具只要支持自定义 base_url 和 api_key,就能直接接进去。
你需要先拿到一个可用的 Key。进入控制台创建 API Key,建议按用途命名,比如openclaw-imessage,方便以后轮换。创建入口在控制台的 API Keys 页面,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。拿到形如sk-...的字符串后,不要写进会提交到 git 的文件,优先用环境变量注入。
模型通道的 base_url 用 https://taotoken.net/api ,注意这个地址不带任何查询参数。openclaw 的配置里如果要求填base_url或api_base,就填这个;如果要求填完整的 chat completions 路径,就在后面拼/v1/chat/completions。两种写法取决于 openclaw 版本对 OpenAI 兼容层的处理方式,下面配置骨架里我会给出可切换的写法。
如果你后面要把 openclaw 长期挂在后台跑编码类或 Agent 类任务,可以考虑 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。它更适合高频、长会话的场景,普通 iMessage 问答用按量 Key 就够了。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,配置字段对不上时以文档为准。
3. 可复制配置:config.toml 与 settings.json 骨架
openclaw 的配置分两层:config.toml管通道和运行时,settings.json管模型 provider。下面这份骨架可以直接抄,把占位符替换成你自己的值。先看config.toml:
# ~/.openclaw/config.toml [gateway] log_level = "info" auto_restart = true max_restart = 10 [channels.imessage] enabled = true # 指向 openclaw 自带的 imsg rpc 可执行文件 rpc_bin = "/usr/local/bin/openclaw-imsg" # chat.db 路径,注意用真实路径,不要用软链接 db_path = "/Users/yourname/Library/Messages/chat.db" # RPC 启动超时,权限没配好时这里会先超时 ready_timeout_ms = 30000 [channels.imessage.model] provider = "taotoken" model = "gpt-4o-mini" # 模型请求超时,和 RPC 超时分开 request_timeout_ms = 60000再看settings.json,这里放 provider 定义。openclaw 读取顺序通常是先环境变量、再 settings.json,所以 Key 建议只放环境变量:
{ "providers": { "taotoken": { "type": "openai-compatible", "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "chat_path": "/v1/chat/completions", "default_model": "gpt-4o-mini", "headers": { "Content-Type": "application/json" } } }, "defaults": { "provider": "taotoken", "temperature": 0.7, "max_tokens": 1024 } }环境变量这样注入,写进~/.zshrc或启动脚本里:
export TAOTOKEN_API_KEY="sk-你的Key"注意db_path一定写真实路径。如果你用 nvm 或 Homebrew 装的运行时,openclaw 实际调用的二进制路径可能和which出来的不一样,这一点在第 5 节排障里会展开。配置改完先别急着启动,下一步先做最小验证,确认 permission 报错是否消失。
4. 验证请求:用最小动作确认 permission 报错消失
验证分两步,先验证 chat.db 能读,再验证模型通道能通。两步都过了,再启动完整 gateway。
第一步,绕过 openclaw,直接用 sqlite3 读一下 chat.db,确认权限是否生效:
sqlite3 /Users/yourname/Library/Messages/chat.db "SELECT COUNT(*) FROM message LIMIT 1;"如果返回一个数字,说明完全磁盘访问权限已经对当前终端生效。如果返回Error: unable to open database file或authorization denied,说明权限还没落到正确的二进制上,回到第 5 节排查。这一步很关键,它把“权限问题”和“配置问题”彻底分开。
第二步,用 curl 直接打 TaoToken 的 chat completions,确认 Key 和 base_url 可用:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'正常会返回一个 JSON,choices[0].message.content里有内容。如果返回 401,检查 Key 是否带上了Bearer前缀;如果返回 404,检查 base_url 和 chat_path 拼接后是不是https://taotoken.net/api/v1/chat/completions。这一步通了,说明模型通道没问题。
第三步,启动 openclaw 的 iMessage 通道,观察日志:
openclaw gateway --channel imessage --log-level debug成功时你应该看到类似imsg rpc ready和channel imessage started的日志,不再出现failed to parse permissionDenied。如果 permission 报错消失但模型调用报错,那就是 settings.json 的字段问题,回到第 3 节核对。想快速验证模型本身是否可用,也可以直接在模型对话页面发一条消息,入口在 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。
5. 本篇常见错排查:permission 报错反复出现时看这几处
路径混淆(Symlink vs Real Path)。macOS 的 TCC 按二进制真实路径校验。你用 nvm 装的 Node,实际路径可能是~/.nvm/versions/node/v24.x/bin/node;Homebrew 装的 Python 可能是/opt/homebrew/opt/python@3.12/bin/python3。在完全磁盘访问权限列表里加的是 Terminal,但 openclaw 实际 fork 的是另一个二进制,权限就对不上。用which node、which python3找到真实路径,把那个二进制加进列表。
自动化权限缺失。部分 iMessage 工具除了读 chat.db,还会通过 AppleScript 控制“信息”App。如果日志里除了 permission 还有 AppleScript 相关报错,去“隐私与安全性 - 自动化”里确认你的终端或 openclaw 被允许控制“信息”。
权限改了但进程没重启。TCC 权限对已运行进程不立即生效。彻底 Ctrl+C 停掉 gateway,确认没有残留进程(ps aux | grep openclaw),关掉终端窗口重开,再启动。只重启 gateway 不重启终端,权限上下文可能还是旧的。
JSON 解析器本身太脆。openclaw 的 RPC 层如果直接把系统回包丢给JSON.parse,遇到纯文本就崩。可以在你的包装脚本里加一层容错,先判断首字符是不是{或[,不是就打印原始回包并退出,避免无限重启:
function safeParse(raw) { const s = raw.trim(); if (!s.startsWith("{") && !s.startsWith("[")) { console.error("非 JSON 回包,疑似权限错误:", s.slice(0, 200)); process.exit(1); } return JSON.parse(s); }Key 或 base_url 写错导致误判。有时候 permission 报错已经消失,但模型调用 401,日志里混着旧报错,容易以为权限没修好。分开验证:先跑第 4 节的 sqlite3 和 curl,两个都过再启动 gateway。接入相关的字段问题,以接入文档为准:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
安全提醒。chat.db 含全部聊天记录和元数据,只给信任的工具授权,定期审查完全磁盘访问权限列表,移除不再用的条目。开源项目在授权前简单看一眼它读 chat.db 之后把数据发去哪,别让聊天记录流向不明服务。
6. 把权限和模型通道分开维护,openclaw 的 iMessage 机器人就稳了
这套排查下来,最省时间的做法是把两件事拆成两条独立验证线:一条是sqlite3读 chat.db,一条是curl打 TaoToken。两条都绿了再启动 gateway,日志里就不会再出现 permission 混进 JSON 的级联崩溃。配置骨架里的db_path用真实路径、Key 走环境变量、base_url 用 https://taotoken.net/api ,这三条固定下来,以后换模型或换机器都只是改几个字段的事。长期跑编码或 Agent 任务时再考虑 Coding Plan,日常 iMessage 问答按量 Key 足够。