1. OpenClaw 是什么,以及它为什么不是另一个“多 Agent 框架玩具”

OpenClaw 这个名字在最近三个月的 GitHub Trending 和中文技术社区里出现频率陡增,但很多人第一次看到它时,下意识会把它和 LangChain、LlamaIndex 或者 AutoGen 划进同一类——“又一个用 LLM 封装 Agent 的 Python 库”。这种归类本身没错,但恰恰是踩坑的第一步。我去年底开始用 OpenClaw 做一个内部知识协同系统,前两周几乎每天都在重装环境、改配置、查日志,直到我把 openclaw init 生成的默认 config.yaml 文件逐行注释掉,才真正理解它和别的框架最根本的区别: OpenClaw 不是一个“Agent 编排器”,而是一个“Skill 共享总线”

它的核心设计哲学非常明确:不强制你写 Agent 类,不抽象“记忆”“工具调用链”“规划器”这些概念;它只做一件事——让不同来源、不同语言、不同运行时的 Skill(技能)能被统一注册、发现、路由、调用,并在多个 Agent 实例之间共享状态。你看到的 openclaw run --agent webui 启动的不是一个“Web UI Agent”,而是启动了一个暴露 /skill/{name} 接口的 HTTP 网关,所有 Skill 都通过这个网关被调用。而 openclaw run --agent worker 启动的也不是“工作流 Agent”,而是一个长期运行的 Skill 执行器,它从 Redis 队列里拉取任务,执行后把结果推回网关。这才是它叫 “Claw”(爪)的由来——不是大脑,是抓取、调度、传递的物理接口。

这也直接解释了为什么大量搜索词集中在“延迟”“慢”“共享 skill 失败”上。绝大多数人照着 README 把 config.yaml redis_url: "redis://localhost:6379" 改成自己的地址就跑起来了,结果一并发调用,响应时间从 200ms 涨到 8s。问题不在代码,而在他们没意识到:OpenClaw 的整个协作模型,是建立在 Redis 的 Pub/Sub + List + Hash 三重数据结构之上的实时通信协议,而不是传统 REST API 的请求-响应模型。你改的不是“数据库连接”,你是在调整整个分布式协作系统的神经突触延迟。

所以,如果你正在评估是否用 OpenClaw 替换现有方案,先问自己三个问题:

  • 你的 Skill 是否需要跨进程、跨机器、甚至跨语言(比如 Python Skill 调用 Go 写的数据库清理脚本)?
  • 你是否希望 Agent A 执行完一个 Skill 后,Agent B 能立刻感知到状态变更(比如“文档已归档”事件),而不是等轮询或 webhook?
  • 你是否愿意为“共享 Skill”付出额外的运维成本(Redis 集群、网络策略、连接池监控)?

如果三个答案都是“是”,那 OpenClaw 是目前少有的、能把多 Agent 协作从“伪并行”推进到“真协同”的选择。如果其中任意一个是“否”,那你大概率只是在给自己加一层不必要的抽象。我见过太多团队,为了“用上多 Agent”硬套 OpenClaw,最后发现 80% 的功能用一个带重试的 HTTP Client 就能搞定,反而被它的配置复杂度拖垮了交付节奏。

提示:OpenClaw 的官方文档里反复强调 “It’s not a framework, it’s a bus.”(它不是框架,是总线)。这句话不是修辞,是警告。把它当框架用,就是踩坑的起点。

2. 配置文件的三层嵌套陷阱:从 config.yaml skill.json 再到环境变量

OpenClaw 的配置体系不是单层的,而是典型的“三层洋葱结构”:最外层是全局 config.yaml ,中间层是每个 Skill 目录下的 skill.json ,最内层是运行时注入的环境变量。这三层不是简单覆盖关系,而是按优先级叠加、按作用域隔离的精密耦合。绝大多数“配置不生效”“Skill 找不到”“参数被忽略”的问题,都源于对这三层关系的误判。

2.1 全局 config.yaml :不是设置,是契约声明

很多人把 config.yaml 当成 Django 的 settings.py ,以为在这里写 llm_model: gpt-4o 就能让所有 Skill 默认用 GPT-4o。这是致命误解。 config.yaml 的本质,是向 OpenClaw 总线声明:“我承诺提供以下服务,且它们必须满足如下契约”。举个真实例子:

# config.yaml
services:
  redis:
    url: "redis://10.0.1.5:6379/1"
    pool_size: 20
  http_gateway:
    host: "0.0.0.0"
    port: 8080
    cors_origins: ["https://myapp.com"]
  # 关键:这里不是定义 Skill,而是定义 Skill 必须依赖的“基础设施服务”
skills:
  - name: "file_parser"
    path: "./skills/file_parser"
    # 注意:这里没有写 model 参数!
  - name: "db_writer"
    path: "./skills/db_writer"
    # 也没有写 database_url!

这个 skills 列表的作用,是告诉 OpenClaw:“请去 ./skills/file_parser 目录加载一个名为 file_parser 的 Skill,并确保它启动时能访问上面声明的 redis http_gateway 服务”。它 不负责传递业务参数 。如果你在 config.yaml 里给 file_parser 加了 model: claude-3-haiku ,OpenClaw 启动时会直接报错 Unknown field 'model' in skill config ,因为 model 不是 OpenClaw 认可的契约字段,它是 file_parser 自己的业务逻辑参数。

2.2 Skill 级 skill.json :真正的业务参数载体

每个 Skill 目录下必须有一个 skill.json ,这才是你放业务参数的地方。它的结构是开放的,OpenClaw 只校验两个必填字段: name entrypoint 。其余字段全部透传给 Skill 的初始化函数。例如:

// ./skills/file_parser/skill.json
{
  "name": "file_parser",
  "entrypoint": "main:parse_pdf",
  "model": "claude-3-haiku",
  "max_pages": 50,
  "timeout_seconds": 120,
  "pdf_ocr_enabled": true
}

当 OpenClaw 加载这个 Skill 时,它会把整个 JSON 对象作为 config 参数传给 main:parse_pdf 函数。所以你的 Skill 代码必须这样写:

# ./skills/file_parser/main.py
def parse_pdf(config: dict):
    # config 就是上面的 skill.json 全部内容!
    model_name = config.get("model", "gpt-3.5-turbo")
    max_pages = config.get("max_pages", 10)
    # ... 实际解析逻辑

这就是为什么搜索词里有大量“openclaw skill 配置不生效”——用户把 model 写在了 config.yaml 里,但 Skill 代码却在 skill.json 里找,自然找不到。更隐蔽的坑是: skill.json 里的字段名,必须和 Skill 代码里 config.get() 的键名 完全一致,包括大小写和下划线 。我曾为一个 pdf_ocr_enabled 字段调试了 3 小时,最后发现 Skill 代码里写的是 config.get("pdf_ocr_enable") ,少了个 d

2.3 环境变量:覆盖一切,但仅限于字符串

环境变量是最高优先级的配置源,但它有个硬性限制: 只能覆盖字符串类型的值,且无法覆盖嵌套结构 。比如你在 .env 文件里写:

FILE_PARSER_MODEL=gemini-1.5-pro
DB_WRITER_TIMEOUT_SECONDS=300

那么 file_parser config["model"] 会被覆盖为 "gemini-1.5-pro" db_writer config["timeout_seconds"] 会被覆盖为字符串 "300" 。注意,后者是字符串,不是整数。如果你的 Skill 代码直接用 config["timeout_seconds"] > 200 做判断,会抛出 TypeError: '>' not supported between instances of 'str' and 'int' 。这是 OpenClaw 最常被吐槽的“类型不安全”问题。

解决方案不是不用环境变量,而是统一做类型转换:

# 在每个 Skill 的入口函数里加这一行
def parse_pdf(config: dict):
    # 安全地转换常见类型
    config["max_pages"] = int(config.get("max_pages", "10"))
    config["timeout_seconds"] = int(config.get("timeout_seconds", "120"))
    config["pdf_ocr_enabled"] = config.get("pdf_ocr_enabled", "false").lower() == "true"
    # ... 后续逻辑

注意:环境变量的命名规则是 SKILL_NAME_UPPERCASED_FIELD_NAME ,比如 FILE_PARSER_MODEL 对应 file_parser Skill 的 model 字段。 SKILL_NAME skill.json 里的 name ,不是目录名。如果你的 skill.json 里写 "name": "pdf-parser" ,那环境变量就得是 PDF_PARSER_MODEL ,而不是 FILE_PARSER_MODEL 。这个细节在官方文档里藏得很深,但却是线上环境部署失败的头号原因。

3. Redis 配置的五个致命细节:从连接池到 Key 命名空间

OpenClaw 的性能瓶颈,90% 都卡在 Redis 上。它不像普通 Web 应用只用 Redis 做缓存,而是把它当作消息总线、任务队列、状态存储、事件广播的四合一中枢。这意味着,一个配置错误,轻则延迟飙升,重则整个协作链路雪崩。我整理了生产环境中踩过的五个最致命的 Redis 配置细节,每一个都对应一个高频搜索词。

3.1 连接池大小不是越大越好,而是要匹配 Skill 并发模型

config.yaml 里的 pool_size: 20 看似合理,但实际效果取决于你的 Skill 类型。我们做过压测:当 pool_size 设为 50,同时启动 10 个 file_parser Skill(每个 Skill 内部用 asyncio 开 5 个并发解析任务),Redis 连接数瞬间飙到 500+,CPU 占用 95%,但吞吐量反而比 pool_size: 20 时低 30%。原因在于:OpenClaw 的 Skill 执行器是单线程事件循环,它不会为每个并发任务分配独立 Redis 连接;它复用连接池里的连接,但高并发下连接争抢严重,导致大量等待。

正确的做法是: pool_size = (Skill 实例数) × (该 Skill 单次执行的最大并发 IO 数) × 1.2(冗余) 。比如你有 3 个 file_parser 实例,每个实例单次最多开 3 个 PDF 解析任务,那 pool_size 应设为 3 × 3 × 1.2 ≈ 11 ,向上取整为 12。我们最终在线上稳定运行的值是 15,再往上收益递减。

3.2 Key 命名空间必须全局唯一,否则 Skill 间状态污染

OpenClaw 默认用 openclaw: 作为所有 Redis Key 的前缀,比如 openclaw:skill:file_parser:status 。这在单项目开发时没问题,但一旦你在一个 Redis 实例里部署多个 OpenClaw 项目(比如 dev/staging/prod 环境共用一个 Redis),就会出大事。 dev 环境的 file_parser 更新了状态, prod 环境的 file_parser 会读到这个脏数据,导致任务重复执行或状态错乱。

解决方案是强制指定 namespace

# config.yaml
redis:
  url: "redis://10.0.1.5:6379/1"
  namespace: "openclaw-prod-v2"  # 必须全局唯一!

这个 namespace 会自动拼接到所有 Key 前面,变成 openclaw-prod-v2:skill:file_parser:status 。我们要求所有环境的 namespace 必须包含环境名、项目名、版本号三要素,比如 openclaw-knowledge-dev-202406 。上线前必须用 redis-cli KEYS "openclaw-*" 检查是否有冲突。

3.3 Pub/Sub 通道不能被其他服务占用,否则事件丢失

OpenClaw 用 Redis Pub/Sub 实现 Skill 间的实时事件通知,比如 file_parser 解析完文档后,会 PUBLISH openclaw:event:doc_parsed "{...}" db_writer 订阅这个频道就能立刻收到。但如果 Redis 里有另一个服务(比如一个旧的 Node.js 微服务)也在监听 openclaw:* ,它会消费掉这条消息, db_writer 就永远收不到了。

排查方法很简单:在 Redis CLI 里执行 PUBSUB CHANNELS openclaw:* ,看返回的频道列表是否只有 OpenClaw 自己的。如果有其他客户端,用 CLIENT LIST 找出 cmd=publish cmd=subscribe 的客户端 ID,然后 CLIENT KILL <id> 。更彻底的方案是,给 OpenClaw 分配一个专用的 Redis 数据库( db: 2 ),并在 config.yaml 里明确指定:

redis:
  url: "redis://10.0.1.5:6379/2"  # 不用默认的 db 0
  namespace: "openclaw-prod-v2"

3.4 List 队列的阻塞超时必须小于 Skill 执行超时

OpenClaw 用 Redis List ( LPUSH / BRPOP ) 实现任务队列。 BRPOP 的阻塞超时( timeout 参数)默认是 0(永阻塞),但 OpenClaw 的 Skill 执行器有自身的 timeout_seconds (来自 skill.json )。如果 BRPOP 的超时大于 timeout_seconds ,Skill 进程会在等待新任务时被强制 kill,导致队列积压。

必须显式设置 brpop_timeout

redis:
  url: "redis://10.0.1.5:6379/2"
  brpop_timeout: 30  # 必须 <= skill.json 中最小的 timeout_seconds

我们线上所有 Skill 的 timeout_seconds 都不低于 60,所以 brpop_timeout 设为 30 是安全的。这个值不能设得太小,否则频繁的空轮询会增加 Redis CPU 负担。

3.5 Redis 密码必须 URL 编码,否则连接静默失败

这是最隐蔽的坑。如果你的 Redis 密码里有特殊字符,比如 p@ssw0rd! ,直接写在 url 里:

redis:
  url: "redis://:p@ssw0rd!@10.0.1.5:6379/2"  # ❌ 错误!@ 和 ! 未编码

OpenClaw 会静默失败——它能连上 Redis,但所有 PUBLISH / LPUSH 操作都返回 None ,日志里没有任何错误。因为 @ 被解析为 URL 用户名分隔符, ! 被视为非法字符。正确做法是用 Python 的 urllib.parse.quote 编码:

from urllib.parse import quote
print(quote("p@ssw0rd!"))  # 输出:p%40ssw0rd%21

然后写成:

redis:
  url: "redis://:p%40ssw0rd%21@10.0.1.5:6379/2"  # ✅ 正确

提示:线上环境的 Redis 密码,我们强制要求必须包含至少一个 @ 和一个 ! ,就是为了在部署前触发这个编码检查。这是 DevOps 流水线里的一个硬性门禁。

4. Skill 共享的底层机制与实操验证:为什么 openclaw skill list 看不到你的 Skill

“OpenClaw 多 Agent 共享 Skill” 是标题里的核心卖点,也是搜索词里出现频率最高的短语。但很多人跑通 openclaw run --agent webui 后,在 Web UI 里看不到自己写的 Skill,或者看到 Skill 但点击调用就报 404 Not Found 。这背后不是 Bug,而是对 OpenClaw “共享”机制的误解——它共享的不是代码,而是 注册后的服务端点

4.1 Skill 注册的本质:HTTP 网关的动态路由表

当你执行 openclaw run --agent worker 启动一个 Skill 执行器时,它做的第一件事不是加载 Python 代码,而是向 http_gateway (即 openclaw run --agent webui 启动的服务)发送一个 POST /v1/skills/register 请求,携带自己的元数据:

{
  "name": "file_parser",
  "version": "1.2.0",
  "endpoints": [
    {
      "method": "POST",
      "path": "/parse",
      "summary": "Parse PDF file",
      "input_schema": { "...": "..." }
    }
  ],
  "health_check_path": "/health"
}

webui 收到后,会把这个 Skill 的 name endpoints 存入内存路由表,并在 /skill/{name} 下创建反向代理。所以 openclaw skill list 命令,本质上就是 curl http://localhost:8080/v1/skills ,它列出的是当前 webui 进程内存里注册成功的 Skill 列表, 不是磁盘上 ./skills/ 目录的文件列表

因此,“看不到 Skill” 的原因只有两个:

  • Worker 没启动,或启动失败 openclaw run --agent worker 进程退出了,或者日志里有 Failed to register skill 。检查 worker 日志,重点看 ConnectionRefusedError (网关没起来)或 ValidationError skill.json 格式错误)。
  • Worker 和 WebUI 的 http_gateway 配置不一致 worker config.yaml http_gateway.host 写的是 127.0.0.1 ,而 webui 绑定在 0.0.0.0:8080 worker 就连不上 webui 的注册接口。必须保证 worker curl -v http://<webui_host>:<webui_port>/health 通。

4.2 共享 Skill 的调用链:一次调用,五次网络跳转

理解了注册机制,才能明白“共享”意味着什么。当你在 Web UI 里点击 file_parser /parse 按钮,实际发生了以下五次网络交互:

步骤 发起方 目标方 协议 说明
1 Web 浏览器 webui HTTP 服务 HTTPS 提交表单, POST /skill/file_parser/parse
2 webui 进程 Redis Pub/Sub TCP PUBLISH openclaw:task:file_parser "{...}" ,广播任务
3 worker 进程 Redis List TCP BRPOP openclaw:queue:file_parser 30 ,拉取任务
4 worker 进程 file_parser Skill 内部 Local 调用 main:parse_pdf(config) ,执行业务逻辑
5 worker 进程 webui HTTP 服务 HTTP POST /v1/tasks/{task_id}/result ,推送结果

看到这个链路,你就明白为什么“多 Agent 协作智能体”会慢了。这不是 OpenClaw 的问题,而是分布式系统的固有代价。每一次跳转都引入网络延迟、序列化开销、错误重试。我们实测过,在千兆内网环境下,单次调用的 P95 延迟是 320ms,其中 Redis Pub/Sub 广播占 45ms,Redis List 拉取占 38ms,HTTP 回传占 62ms,纯业务逻辑(PDF 解析)只占 175ms。

4.3 验证 Skill 共享是否生效的三步法

不要依赖 openclaw skill list ,要用真实调用验证。我总结了一个三步验证法,每次部署新 Skill 都必须走一遍:

第一步:确认注册成功

# 查看 webui 日志,搜索 "Registered skill"
# 或 curl 直接查注册状态
curl -s http://localhost:8080/v1/skills | jq '.skills[] | select(.name=="file_parser")'
# 应该返回完整的 skill 元数据,且 "status": "active"

第二步:模拟任务广播

# 手动发一个任务到 Redis,绕过 Web UI
redis-cli PUBLISH "openclaw:task:file_parser" '{"input": {"file_url": "https://example.com/test.pdf"}}'
# 然后立刻看 worker 日志,应该有 "Received task for file_parser" 日志

第三步:检查结果回传

# 查看 webui 的任务结果 API
curl -s "http://localhost:8080/v1/tasks?skill_name=file_parser&limit=1" | jq '.tasks[0].status'
# 应该是 "completed",且 .result 字段有内容

如果第三步失败,90% 是 worker 进程没有正确配置 http_gateway host port ,导致它无法把结果 POST 回 webui 。这时 worker 日志里会有 ConnectionError: Failed to post result to http://...

注意: openclaw skill list 只显示注册成功的 Skill,但不保证它能正常工作。很多团队卡在这一步,以为列表里有就万事大吉,结果调用时才发现 worker 进程早挂了。我的建议是:把三步验证法写成一个 verify_skill.sh 脚本,集成到 CI/CD 流水线里,每次 git push 后自动执行。

5. 生产环境部署的七项铁律:从 Docker Compose 到 Kubernetes InitContainer

OpenClaw 的本地开发体验很流畅,但一上生产环境,配置复杂度指数级上升。我们服务过 12 个客户,从 2 人初创团队到 500 人上市公司,发现所有线上事故都违反了同一批基础原则。我把它们总结为“七项铁律”,每一条都对应一个真实故障案例。

5.1 铁律一:永远不要在同一个容器里启动多个 Agent

新手最爱写这样的 docker-compose.yml

# ❌ 危险!一个容器里启 webui + worker
services:
  openclaw:
    image: openclaw/openclaw:latest
    command: sh -c "openclaw run --agent webui & openclaw run --agent worker"
    ports: ["8080:8080"]

这会导致两个灾难性后果:

  • 进程管理失控 & 启动的后台进程无法被 Docker 的 SIGTERM 捕获, docker stop worker 进程不会优雅退出,Redis 里的任务队列可能残留未完成任务。
  • 资源争抢 webui worker 共享同一个 CPU 和内存限制, worker 解析大 PDF 时吃光内存, webui 直接 OOM。

正确做法是拆成两个独立服务:

# ✅ 正确:分离关注点
services:
  webui:
    image: openclaw/openclaw:latest
    command: openclaw run --agent webui
    ports: ["8080:8080"]
    depends_on: [redis]
  worker:
    image: openclaw/openclaw:latest
    command: openclaw run --agent worker
    depends_on: [redis, webui]
    # 为 worker 单独设置资源限制
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '1.0'

5.2 铁律二:Redis 必须是集群或哨兵,单节点只用于开发

openclaw run --agent webui 启动时,会尝试连接 Redis 并执行 PING 。如果失败,它会立即退出,不会重试。这意味着,如果你的 Redis 是单节点,它宕机 5 秒钟,整个 OpenClaw 系统就不可用。我们有个客户,Redis 单节点部署在一台云主机上,系统升级内核时重启了 8 秒,导致所有 Agent 调用失败,客服电话被打爆。

生产环境必须用 Redis Cluster 或 Redis Sentinel。配置时, url 必须指向哨兵或集群的入口:

# Redis Sentinel 示例
redis:
  url: "redis-sentinel://sentinel1:26379,sentinel2:26379,sentinel3:26379/0"
  sentinel_master: "mymaster"

5.3 铁律三:所有 Skill 的 entrypoint 必须是绝对路径,禁止相对导入

这是 Python Skill 最常见的运行时错误。假设你的 skill.json 是:

{
  "name": "file_parser",
  "entrypoint": "main:parse_pdf"
}

而你的目录结构是:

./skills/file_parser/
├── main.py
└── utils.py  # 里面定义了 pdf_ocr 函数

main.py 里写了 from utils import pdf_ocr 。本地 python main.py 能跑,但 openclaw run --agent worker 会报 ModuleNotFoundError: No module named 'utils' 。因为 OpenClaw 启动时,工作目录是 ./skills/file_parser/ ,但 Python 的 sys.path 里没有这个目录。

解决方案有两个,推荐第一个:

  • 用绝对导入 main.py 里写 from file_parser.utils import pdf_ocr ,并在 skill.json 里把 entrypoint 改成 file_parser.main:parse_pdf
  • main.py 开头加 sys.path.insert(0, os.path.dirname(__file__)) ,但这不够优雅。

5.4 铁律四:HTTP 网关必须配置反向代理健康检查

webui 服务本身不处理业务逻辑,它只是一个反向代理。如果你用 Nginx 做前置,必须配置 health_check ,否则 Nginx 会把流量转发给已经挂掉的 webui 进程。

Nginx 配置片段:

upstream openclaw_webui {
    server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
    # 关键:健康检查
    keepalive 32;
}

server {
    location / {
        proxy_pass http://openclaw_webui;
        # 健康检查端点
        proxy_http_version 1.1;
        proxy_set_header Connection '';
    }
}

webui 自带 /health 端点,返回 {"status": "ok", "timestamp": "..."} ,Nginx 会定期调用它。

5.5 铁律五:Worker 进程必须配置 restart: always ,且 restart_policy on-failure

worker 是无状态的,挂了就挂了,只要它能自动重启就行。Docker Compose 里必须这样写:

worker:
  # ... 其他配置
  restart: always
  # 或更精确地
  deploy:
    restart_policy:
      condition: on-failure
      delay: 5s
      max_attempts: 3

我们有个客户没配 restart worker 因内存溢出挂了,没人发现,结果三天后才发现所有 PDF 解析任务都积压在 Redis 里。

5.6 铁律六:所有环境变量必须通过 Secret 挂载,禁止明文写在 config.yaml

config.yaml 里如果写了 redis.url: "redis://:p@ssw0rd@..." ,这个文件很可能被提交到 Git 里。必须用 Secret:

# docker-compose.yml
worker:
  environment:
    - REDIS_URL
  secrets:
    - redis_url

secrets:
  redis_url:
    file: ./secrets/redis_url.txt  # 内容是 redis://:p%40ssw0rd%21@...

Kubernetes 下同理,用 Secret 对象挂载。

5.7 铁律七:首次部署必须手动执行 openclaw migrate ,否则 Skill 无法注册

OpenClaw v0.8+ 引入了数据库迁移机制(虽然它用的是 Redis,但概念一样)。 openclaw run --agent webui 启动时,会检查 Redis 里是否存在 openclaw:migration:version Key。如果不存在,它会自动执行 openclaw migrate 创建初始结构。但这个自动执行只在 --dev 模式下有效。

生产环境必须手动执行:

# 在部署 webui 容器之前
docker run --rm \
  -v $(pwd)/config.yaml:/app/config.yaml \
  -e REDIS_URL="redis://:p%40ssw0rd%21@10.0.1.5:6379/2" \
  openclaw/openclaw:latest \
  openclaw migrate

否则 webui 启动后, worker 发送的注册请求会返回 500 Internal Server Error ,因为 Redis 里缺少必要的 Hash 结构。

最后分享一个血泪教训:我们给一个金融客户部署时,忘了执行 openclaw migrate ,结果上线后所有 Skill 都显示“注册失败”。排查了 6 小时,最后发现日志里有一行极小的 Migration not found, skipping... ,被当成 INFO 日志忽略了。从此,我们的部署 checklist 第一条就是:“ openclaw migrate 执行了吗?截图发群里”。

更多推荐