OpenClaw教程
Skills调试
性能优化
故障排查
Agent Skills
🎬 开篇:bug是AI的成人礼
凌晨4点17分,我和这个bug对视了整整一个时辰。
不是因为代码写错了——代码是AI写的,AI说没问题。问题就在于,AI说"没问题"的时候,往往就是最大的问题。
这个Skill在我本地跑得好好的,一上ClawHub就挂。日志报的错和代码完全对不上。那一刻我突然明白:调试Skills,不是改代码,是破案。
📖 为什么 Skills 需要专门调试?
OpenClaw Skills 和普通的Node.js/Python代码不同:
- 运行环境隔离:Skills在沙箱或独立runtime中运行,本地环境和生产环境可能完全不同
- 权限系统:
openclaw.json中的 permissions 决定能调哪些工具,缺权限会静默失败 - 异步+并发:Agent可能同时触发多个Skills,竞态条件难以复现
- 外部依赖:API限流、网络抖动、第三方服务不稳定
⚠️ 血泪教训:不要相信"在我机器上能跑"。Skills发布前,必须在类生产环境测试。我见过一个Skill因为时区问题,在UTC+8的服务器上每天凌晨挂一次——整整挂了3天才发现。
🛠️ 调试工具箱
1. 日志:你的第一双眼
OpenClaw提供了结构化日志,但很多人不会用:
// ❌ 错误示范:随便console.log
console.log('data:', data);
// ✅ 正确做法:结构化日志
console.log(JSON.stringify({
level: 'info',
skill: 'my-skill',
action: 'fetch-data',
status: 'success',
data: data,
timestamp: new Date().toISOString()
}));
// ✅ 更简单的做法:用OpenClaw的logger(如果可用)
const logger = require('@openclaw/logger');
logger.info('fetch-data', { status: 'success', data });
2. 调试模式运行
# 以调试模式运行Skill openclaw skills run my-skill --debug # 查看Skill运行日志 openclaw logs --skill=my-skill --tail=100 # 查看特定session的日志 openclaw logs --session=abc123
3. 本地复现生产问题
// 使用和生产环境相同的配置
const config = {
runtime: process.env.OPENCLAW_RUNTIME || 'sandbox',
timeout: 30000,
permissions: ['web_fetch', 'exec'] // 模拟生产权限
};
// 模拟网络延迟和异常
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.log(`尝试 ${i+1}/${retries} 失败:`, err.message);
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); // 指数退避
}
}
}
🔍 常见错误与排查清单
问题1:Skill加载失败
症状:Skill not found 或 Failed to load skill
排查步骤:
# 1. 检查skill目录结构
ls -la skills/my-skill/
# 应该至少有:openclaw.json, index.js
# 2. 验证openclaw.json格式
cat skills/my-skill/openclaw.json | jq .
# 3. 检查依赖是否安装
cd skills/my-skill && npm install
# 4. 尝试手动require
node -e "console.log(require('./skills/my-skill'))"
✅ 解决:90%的情况是 openclaw.json 格式错误或缺少依赖。
问题2:权限错误(静默失败)
症状:Skill运行无报错,但某些功能不生效
排查:
// 检查当前Skill的权限
const skillMeta = require('./openclaw.json');
console.log('声明的权限:', skillMeta.permissions);
// 运行时检查权限(如果OpenClaw提供API)
try {
await browser.action({ action: 'status' });
console.log('browser 权限: ✅');
} catch (err) {
console.log('browser 权限: ❌', err.message);
}
⚠️ 注意:OpenClaw的权限错误有时会静默失败,不会throw Exception!
问题3:超时问题
症状:Execution timeout 或 Skill 跑到一半卡住
// 设置合理的超时
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 25000);
try {
const result = await fetch(url, {
signal: controller.signal,
timeout: 20000
});
clearTimeout(timeout);
return result;
} catch (err) {
if (err.name === 'AbortError') {
console.error('请求超时,url:', url);
}
throw err;
}
❌ 避免:无限等待的异步操作,一定要加timeout。
问题4:内存泄漏
症状:Skill运行多次后变慢,最终OOM崩溃
// 检查内存使用
function logMemory(label) {
const used = process.memoryUsage();
console.log(`${label}:`, {
heapUsed: Math.round(used.heapUsed / 1024 / 1024) + 'MB',
heapTotal: Math.round(used.heapTotal / 1024 / 1024) + 'MB',
rss: Math.round(used.rss / 1024 / 1024) + 'MB'
});
}
// 在关键位置调用
logMemory('开始前');
await doSomething();
logMemory('结束后');
// 常见泄漏点:全局变量、未清理的定时器、未关闭的连接
// ❌ 错误
const cache = {}; // 全局缓存,永不释放
setInterval(() => {}, 1000); // 定时器不清理
// ✅ 正确
const cache = new Map();
cache.set(key, value);
setTimeout(() => cache.delete(key), 60000); // 1分钟后清理
📊 性能优化技巧
1. 减少不必要的工具调用
// ❌ 错误:每次都调用工具
for (const item of items) {
const result = await web_search({ query: item }); // N次搜索!
// ...
}
// ✅ 正确:批量处理
const results = await Promise.all(
items.map(item => web_search({ query: item }))
);
2. 缓存重复计算
const cache = new Map();
async function getDataWithCache(key) {
if (cache.has(key)) {
console.log('缓存命中:', key);
return cache.get(key);
}
const data = await fetchExpensiveData(key);
cache.set(key, data);
return data;
}
3. 控制并发数
// 限制并发,避免被API限流
async function pMap(items, fn, concurrency = 3) {
const results = [];
let index = 0;
async function runNext() {
if (index >= items.length) return;
const i = index++;
try {
results[i] = await fn(items[i], i);
} catch (err) {
results[i] = { error: err.message };
}
await runNext();
}
await Promise.all(
Array.from({ length: Math.min(concurrency, items.length) },
() => runNext()
)
);
return results;
}
// 使用
const results = await pMap(
items,
item => processItem(item),
3 // 最多3个并发
);
🧪 测试策略
好的测试能消灭80%的调试时间:
// 单元测试示例(用你喜欢的测试框架)
describe('My Skill', () => {
it('应该正确处理正常输入', async () => {
const result = await mySkill.process('valid input');
expect(result.success).toBe(true);
});
it('应该优雅地处理错误输入', async () => {
const result = await mySkill.process(null);
expect(result.error).toBeDefined();
});
it('应该在超时后抛出错误', async () => {
await expect(
mySkill.processWithTimeout('input', 1) // 1ms超时
).rejects.toThrow('timeout');
});
});
🎯 黄金法则:每个Skill发布前,至少测试:1) 正常流程;2) 边界条件(空输入、超大输入);3) 异常场景(网络断开、API返回错误);4) 并发场景(同时跑10次)。
🔗 相关资源
🎭 结语:调试是门艺术
世界上有一种能力叫调试,它和写代码正好相反——写代码是"创造",调试是"破案"。
3分37秒,我盯着那个终于跑通的Skill,突然明白:好的调试不是找到bug,而是让bug无处可藏。
愿你的Skills永远不需要调试。但万一需要,这份指南能救你一命。
—— 妙趣AI · 踩坑路上,与你同行