如果你手里已经跑着一个 OpenClaw Agent,想把它接进飞书,让同事在群里喊一句“帮我查下本周数据”就能拿到结果,那这篇文章就是给你准备的。我会从飞书应用的创建、Channel 配置、Skill 目录结构,到一个能用的 SKILL.md 和发送表格/长文本的处理方式,完整走一遍开发流程,并把我实际踩过的报错和排查思路一并整理出来。适合已经能用 OpenClaw 跑通基本对话、正在把它接入真实办公场景的开发者,也适合想理解 Agent Skill 到底是什么的新手。
先说个结论:OpenClaw 接入飞书这件事,真正值得花时间的不是“把机器人跑起来”,而是“把你的能力沉淀成 Skill”。机器人只是一个入口,Skill 才是 Agent 真正干活的本事。
1. 为什么在飞书里开发 OpenClaw Skill
1.1 先分清三个概念:Agent、Channel、Skill
我见过不少人在这一步绕晕。Agent、Channel、Skill 是三件完全不同的事,只是经常被放在同一个配置文件里说。
Agent 是大脑,负责任务理解、拆解、调用工具、组织回复。Channel 是入口,OpenClaw 通过 Channel 连接不同的聊天平台,飞书、Teams、Discord 都属于 Channel。Skill 是能力包,它给 Agent 提供完成特定任务所需的指令、脚本和参考信息。
打个比方:Agent 是一个新入职的员工,飞书机器人是公司前台,Skill 就是这位员工桌上摆着的《工作手册》。前台让访客找到员工,员工靠手册知道“遇到报销怎么处理、遇到数据查询该调哪个脚本”。你开发飞书 Skill,本质上是在给这个 Agent 编写它专属的工作手册。
这套分层设计的好处很直接:一个 Skill 写好了,理论上可以在飞书、Teams、Discord 等不同 Channel 之间复用。我一开始只在飞书里测,后来把同一个 Skill 切到别的 Channel 上,只改配置不动技能逻辑,省了不少事。
1.2 飞书作为 ChatOps 入口的价值
飞书是国内很多团队每天打开次数最多的办公软件,把 Agent 放进去,等于让自动化能力长在团队日常沟通的地方。相比单独开一个网页后台,在飞书群里直接 @ 机器人要数据、提需求,学习成本低得多,使用频率也高得多。
我实际做过的一个项目里,团队成员最常用的是“日报生成”这个 Skill:每天下班前在群里发一句话,机器人自动汇总当天任务、拉取代码提交记录、生成日报草稿。没有这个 Skill 之前,大家得手动打开三四个系统才能凑齐这些信息。接入飞书之后,整个流程被压缩成一次对话。
这也是 Skill 开发最核心的价值判断标准:它能不能把高频、重复、跨系统的操作收敛到一个对话入口里。如果只是让机器人回复一些固定文案,那用飞书自带的消息回复就够了,不值得开发 Skill。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与飞书应用配置
2.1 先跑起一个能用的 OpenClaw 实例
在写任何 Skill 之前,你得有一个能正常对话的 OpenClaw 实例。安装方式现在主要有三种:Docker 容器、官方安装脚本、Windows 桌面端。
我的建议是优先用 Docker,原因只有一个:隔离干净。OpenClaw 依赖 Node 运行时和一些系统库,直接装在宿主机上,升级或者换版本时很容易遇到依赖冲突。Docker 方式把运行时、依赖、会话文件全部封在容器里,出问题直接重建容器,比一个一个排查依赖快得多。
Windows 用户如果不想碰 Docker,也可以用社区里常见的 windowshub 一键安装方式。我实测下来,这种方式对环境变量的管理比较友好,适合本地开发调试。不管用哪种方式,装完后先跑一个最简单的对话测试,确认 Agent 能正常回复,再继续往下做。
安装之后,建议看一眼 OpenClaw 的配置文件格式。不同版本的配置字段会有些差异,但大方向一致:一个 config 文件定义 Agent 使用的模型、Channel 列表、Skill 目录路径。后续加飞书 Channel、加 Skill,都是在这个基础上扩展。
2.2 飞书开放平台创建应用并开启机器人
接下来去飞书开放平台创建企业自建应用。这块步骤不算复杂,但有几个细节直接影响后续能不能收到消息。
前置材料:一个飞书企业管理员账号,或者至少是有创建自建应用权限的账号。
大致流程:
- 登录飞书开放平台,进入“开发者后台”,选择企业,创建企业自建应用。
- 填写应用名称、描述、图标。这里的应用名称就是机器人显示名称,建议直接写明用途,比如“运维助手”“数据小助手”。
- 在“添加应用能力”里开启“机器人”能力,这一步之后应用才会拥有机器人入口。
- 进入“事件订阅”页面,配置事件请求地址和事件类型。
事件订阅是最容易卡住的地方。我建议在开发阶段先用内网穿透工具把本地的 OpenClaw 回调地址暴露出去,拿到一个 https 地址填进去,等验证通过后,再迁到正式服务器。飞书要求回调地址必须能通过 URL 验证,本地调试时不穿透的话,这一关过不了。
需要订阅的事件类型至少要包含 im.message.receive_v1,也就是“机器人接收消息”事件。不订阅这个事件,你在飞书里给机器人发消息它根本不会感知到。
2.3 配置 OpenClaw 的飞书 Channel
应用创建完成后,你会拿到 App ID 和 App Secret,这两个凭证是 OpenClaw 连接飞书的关键。在 OpenClaw 的配置文件里,新增一个飞书 Channel 的配置。常见配置项大致如下:
json复制{
"channels": {
"feishu": {
"type": "feishu",
"app_id": "cli_xxxxxxxxxxxx",
"app_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"verification_token": "xxxxxxxxxxxx",
"encrypt_key": "",
"event_callback_path": "/webhook/feishu"
}
}
}
app_id 和 app_secret 在飞书开放平台的“凭证与基础信息”页面拿。verification_token 和 encrypt_key 在“事件订阅”页面里配置。如果不开消息加密,encrypt_key 留空就行,但 verification_token 建议填上,OpenClaw 回调时会用它校验请求来源。
配置完成后重启 OpenClaw,在飞书中搜索你的机器人应用,给它发一条消息。能正常收到回复,说明 Channel 已经通了,接下来才进入真正的 Skill 开发环节。
2.4 权限管理的坑:必须发布版本才生效
很多人在这一步反复栽跟头:明明已经开通了权限,OpenClaw 调飞书接口还是报 no permission。原因很简单,飞书的权限体系是“申请 + 发布”两步走。你在权限管理页面勾选了权限,只是“申请”,必须把这个版本发布到企业内部,权限才真正对应用生效。
所以每次新增权限,都要重复一遍:开通权限 → 创建版本 → 发布上线。测试期内可以先发布到企业内部测试范围,不用直接全员可见,但“发布”这个动作一定不能省。
3. Skill 核心结构:SKILL.md 与目录组织
3.1 一个 Skill 就是一个目录
现在 Channel 通了,开始写 Skill。OpenClaw 对 Skill 的组织方式非常朴素:每个 Skill 就是一个独立目录,目录名就是技能名,目录里必须有一个 SKILL.md 文件作为入口。
常见结构如下:
text复制skills/
send_daily_report/
SKILL.md
scripts/
generate_report.py
OpenClaw 启动时会扫描 skills 目录,读取每个子目录里的 SKILL.md,根据文件头部的元数据注册这个技能。目录结构决定了技能边界,我把一个技能理解为一个“能独立完成一类任务的最小单元”。日报生成一个目录,报销处理一个目录,别把它们混在一起。
除了 SKILL.md,目录里还可以放 scripts 放脚本、references 放参考资料、templates 放模板文件。这些辅助文件在指令中被引用时,Agent 会自动去对应路径读取。
3.2 SKILL.md 的格式与写法
SKILL.md 是 Agent 理解这个技能的核心文件,格式遵循 Markdown + frontmatter。frontmatter 里的元数据决定技能什么时候被调用,正文里的指令决定技能怎么被调用。
一个典型的 SKILL.md 开头长这样:
yaml复制---
name: send_daily_report
description: 当用户要求生成或发送日报时使用此技能。触发词包括“日报”、“今日汇总”、“工作汇报”。
version: 1.0.0
metadata:
author: yourname
channel: feishu
---
这里要特别强调 description 的写法。Agent 是根据用户消息的语义去匹配技能的,description 写得好不好,直接决定技能能不能被正确触发。我见过有人写“日报技能”,结果 Agent 根本不调用,就是因为描述太笼统。正确做法是写清楚使用场景和触发词,让 Agent 在语义匹配时有足够的信息。
frontmatter 之后是正文指令。这块不需要写代码逻辑,而是写清楚:技能目标是什么、执行流程是什么、有哪些输入参数、输出格式是什么、处理不了时该怎么办。给 Agent 的指令要像给一个新同事写交接文档,假设他完全不了解业务背景。
3.3 从零写一个“发送日报”Skill
我以“发送日报”为例,完整走一遍。这个需求看起来很基础,但它是后续所有复杂 Skill 的骨架。
SKILL.md 内容:
markdown复制---
name: send_daily_report
description: 当用户要求生成日报、发送今日总结、汇总当天工作时使用。触发词包括“日报”、“今日汇总”、“工作汇报”、“daily report”。
version: 1.0.0
metadata:
channel: feishu
---
# 发送日报
## 目标
根据用户提供的今天的工作内容,生成一份结构化日报并发送到飞书。
## 执行流程
1. 如果用户未提供工作内容,主动询问“今天完成了哪些主要工作?”
2. 将用户提供的内容整理为以下结构:
- 今日完成
- 明日计划
- 遇到的问题
3. 调用脚本 `scripts/send_report.py`,传入整理后的内容。
4. 脚本执行成功后,回复用户“日报已发送”。
## 注意事项
- 日报内容必须使用简洁的中文。
- “明日计划”如果用户没有提及,写“待定”。
- 如果脚本执行报错,将错误信息原样返回给用户,不要尝试自行修复。
正文里的指令不需要特别长,但一定要给 Agent 清晰的流程和边界。我自己的经验是,流程写成“如果...那么...”的句式,比写长段落描述更不容易让 Agent 跑偏。
对应的发送脚本 scripts/send_report.py:
python复制import os
import json
import requests
webhook_url = os.environ.get("FEISHU_WEBHOOK_URL")
if not webhook_url:
raise RuntimeError("FEISHU_WEBHOOK_URL 环境变量未设置")
def send_report(content: str) -> dict:
payload = {
"msg_type": "interactive",
"card": {
"header": {"title": {"tag": "plain_text", "content": "日报"}},
"elements": [{"tag": "markdown", "content": content}]
}
}
resp = requests.post(webhook_url, json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
if __name__ == "__main__":
content = sys.argv[1] if len(sys.argv) > 1 else ""
send_report(content)
注意这里的 FEISHU_WEBHOOK_URL 环境变量,不要把 webhook 地址直接写死在代码里。不只是因为这个地址可能轮换,更关键的是,Skill 脚本本身可能被分享到不同 Channel,硬编码会让技能失去复用价值。
3.4 Skill 与 Agent 的分工边界
这是我最想强调的一点:Skill 不是写一段代码让 Agent 执行,而是给 Agent 提供一套“指令 + 工具”的组合。Agent 负责理解用户意图、拆解任务、决定何时调用 Skill,Skill 里的脚本负责执行确定性操作。
举个例子:用户说“帮我把今天下午的会议纪要进一步整理一下发出去”。Agent 需要理解“进一步整理”的含义,结合对话上下文补全信息,然后决定调用哪个 Skill、传什么参数。脚本本身不需要关心语义理解,它只需要负责“把传入的内容格式化并发送”这一件事。
把复杂的业务判断写在脚本里,是我见过最常见的误区。脚本写得越复杂,Agent 越难判断什么时候调用它。反过来,指令写得越清晰,脚本保持简单,整个系统反而越稳定。
4. 飞书消息能力与高阶场景处理
4.1 文本、富文本与卡片,该用哪个
开发 Skill 时,必然要处理“怎么把结果发给用户”这个问题。飞书机器人支持多种消息类型,我实际用得最多的是三种:文本消息、富文本消息、卡片消息。
文本消息最简单,适合纯短信息,比如“日报已发送”。富文本 post 支持分段和标题,比纯文本好看一点,但能力有限。真正适合做 Skill 输出的是 interactive 卡片消息,尤其是 markdown 元素的卡片。
它们之间的差异我整理成了一张对照表:
| 消息类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| text 文本 | 简单通知、状态回报 | 实现简单、兼容性好 | 不支持排版,长文本可读性差 |
| post 富文本 | 多段落文字 | 支持标题和分段 | 交互弱,无按钮无折叠 |
| interactive 卡片 | 结构化结果、操作入口 | 支持 markdown、按钮、折叠 | 需要构造 JSON,排错成本稍高 |
我建议默认用卡片。原因很简单:卡片能把“结果”和“操作”放在一起。比如日报 Skill 可以做成一张卡片,上半部分是日报内容,下半部分是一个“确认发送”按钮,用户点一下按钮才真正发出去。这样比直接输出一条消息更符合真实工作流。
4.2 表格与多维表格的三种落地方式
“飞书机器人发送表格”是很多人找的功能点。这里有一个容易混淆的地方:飞书机器人不能直接“凭空生成一个表格文件”发给你,但通过几种变通方式可以达到效果。
方式一是生成 CSV 或 Excel 文件,通过文件消息发送。这种方式适合“要交给别人打开、编辑”的场景。用 Python 的 openpyxl 生成 xlsx,放到临时目录,再调用飞书文件上传接口拿到 file_key,最后通过 im/v1/messages 发送 file 类型消息。注意飞书对文件消息会有限制,建议控制文件大小在 20MB 以下。
方式二是写飞书多维表格(Base)。这适合“结构化数据需要持续沉淀”的场景。飞书多维表格有完善的开放 API,可以先获取表格的 app_token、table_id,然后用 records 接口逐行写入。我做过一个把监控告警写入多维表格的 Skill,效果是每次告警自动落一条记录,团队可以直接在表格里做二次分析。
方式三是卡片内直接渲染 Markdown 表格。适合轻量展示、不需要用户做后续操作的情况。飞书卡片支持 markdown 元素,表格结构可以直接用 Markdown 语法写在里面。但要注意,当列数太多或单元格过长时,卡片渲染效果会打折扣,数据量大的时候建议还是用前两种方式。
4.3 长文本输出被截断的应对方案
“OpenClaw 在飞书输出容易被截断”这个问题,几乎每个接入飞书的人都会遇到。底层原因有几种:飞书单条消息有长度上限;长文本经过 Markdown 渲染后可能触发超时;OpenClaw 在生成过程中如果花费时间过长,飞书回调接口会有响应超时限制。
应对方案有三个层次。第一,在 Skill 内部主动控制输出长度:需要返回长文本时,拆成多个段落分多条消息发送。第二,把长内容写成在线文档,推送文档链接而不是正文。第三,在卡片里使用折叠模块,默认只展示摘要,用户点击后再展开全文。
我比较推荐第二个方案。生成在线文档的思路是:Skill 脚本调用飞书云文档接口,创建一个文档,填入内容,然后把文档链接通过卡片发给用户。用户点了链接直接在飞书内打开,阅读体验远好于往聊天窗口里倒一大段文本。
顺带提醒一个细节:如果消息内容包含代码,尽量避免用纯文本消息发送。飞书文本消息对代码格式不友好,代码块要么被吞掉换行,要么被自动转义。建议在卡片里用 markdown 元素,并且把代码放在代码块语法里。
4.4 真实场景:飞书触发一个内部流程
热搜里“远程飞书打卡”这类需求,本质上是把飞书当成一个远程触发入口,背后调用内部流程。我用一个更通用的例子来说明:通过飞书机器人触发一个“考勤确认”Skill。
用户在飞书里发一条“帮我登记今天上班”,OpenClaw 匹配到对应 Skill,Skill 脚本调用公司内部的考勤 API,写入一条记录,然后把结果通过卡片返回给用户。整个流程里,飞书只是入口,真正干活的是 Skill 里封装的接口调用。
这里必须强调合规性。涉及企业内部系统的自动化操作,需要确保有明确的授权机制、操作留痕和审计能力。脚本中的鉴权凭证不要写在 Skill 目录里,建议放在统一的密钥管理服务中,Skill 运行时通过环境变量读取。部署这类流程之前,先和所在团队或 IT 部门确认自动化操作是否符合企业内部信息安全规范,这是红线。
5. 高频报错与排查实录
5.1 session file locked (timeout 60000ms)
这个报错我在跑 OpenClaw 时遇到不止一次,典型信息长这样:agent failed before reply: session file locked (timeout 60000ms)。
原因基本都出在会话锁上。OpenClaw 的每个会话对应一个 session 文件,为了保证同一会话不被并发写坏,会加一个文件锁。当多个请求同时命中同一个会话,或者上一次运行异常退出导致锁没有正确释放,新的对话就会一直等待锁,直到 60 秒超时。
排查顺序:先确认是否有多个 OpenClaw 实例在同时运行,如果有,停掉多余实例;然后找到 session 目录,检查是否存在残留的 .lock 文件,手动删掉后重启;最后检查是否有自动化脚本在同时给同一个会话发消息。如果是并发场景,建议在配置里为每个用户会话启用独立的 session,而不是共用默认会话。
5.2 飞书接口报无权限
前面提过,最常见的无权限原因不是配置错了,而是权限没有发布。这里补充一个排查技巧:飞书开放平台的“权限管理”页面里,每个权限旁边会标明“已开通”还是“已发布”。如果显示已开通但接口仍然报无权限,去“版本管理与发布”里创建一个新版本并发布,问题通常立刻解决。
另一种情况是使用了错误的凭证类型。飞书有 tenant_access_token 和 user_access_token 两种令牌,前者是应用身份,后者是用户身份。如果 Skill 脚本里用 user_access_token 调一些本应使用应用身份的接口,也会报无权限。排查时先确认接口文档要求的令牌类型。
5.3 事件回调不触发,消息收不到
如果飞书机器人能发消息但收不到用户消息,问题十有八九出在事件订阅上。打开飞书开放平台对应应用的“事件订阅”页面,看请求地址是否与 OpenClaw 配置一致,再看接收事件里是否包含 im.message.receive_v1。
还有一个容易忽略的细节:飞书开放平台要求回调地址支持 URL 验证,验证通过后,如果后来又修改了 verification_token,需要重新保存并再次验证。开发阶段用内网穿透时,穿透工具的域名地址可能变动,也要同步更新到飞书后台。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 快速解决 |
|---|---|---|
| Agent 不回复 | 会话文件锁残留 | 清理 session 锁文件并重启 |
| 飞书接口无权限 | 权限未发布上线 | 创建版本并发布 |
| 机器人收不到消息 | 未订阅 receive_v1 事件 | 检查事件订阅配置 |
| 长文本输出截断 | 单条消息超限/超时 | 生成在线文档发链接 |
| 表格显示错乱 | 卡片宽度不足 | 改用文件消息发送 |
| Skill 不触发 | description 写得太笼统 | 补全触发场景和触发词 |
6. 一些踩坑后的心得体会
这套流程走下来,我最大的体会是:开发 OpenClaw 的飞书 Skill,核心难点从来不在写代码,而在把“用户意图”和“工具能力”之间的映射关系理清楚。SKILL.md 的指令写得好不好,决定了 Agent 调用技能的成功率;脚本的稳定性,决定了用户对机器人的信任度。两者缺一不可。
调试 Skill 时,我习惯先把关注点放在“技能有没有被正确触发”上,再去看“脚本执行是否正确”。很多人一上来就盯着脚本报错,却忽略了最根本的触发问题。检查触发是否正常,可以先在对话里用触发词直接提问,看 Agent 有没有输出技能相关的回复。没触发就去改 description,触发了再逐行看脚本日志。
另外一个很实用的做法是:每个 Skill 脚本里都加上日志输出,包含接收到的参数、执行过程中的关键状态、最终返回值。飞书这种场景下,你很难实时看 Agent 内部在做什么,日志是你唯一可靠的观察窗口。
最后分享一个小技巧。写 SKILL.md 时,可以在文件末尾加一个“示例对话”段落,把用户可能的提问方式和期望的回答写进去。Agent 在生成回复时会把示例作为风格参考,输出稳定性明显提升,这个做法简单但非常有效,强烈建议试试。
