本教程带你从零构建一个能自动分析应用日志、定位错误根因的 Claude Code 技能(Skill)。围绕这个真实场景,你会掌握 SKILL.md 的完整写法,并真正吃透官方定义的十余个 frontmatter 字段里最关键的几个——disable-model-invocation、arguments、allowed-tools、动态上下文注入 !cmd`、context: fork`。适合写过一两个命令行小项目、想让 Claude Code 长期记住某套固定流程的开发者。学完后你能独立判断一个需求该不该做成技能、该用哪种触发方式。
${CLAUDE_PROJECT_DIR} 等新特性),能跑通 claude 进入交互会话。.log 文件即可,没有的话后面会教你造一段)。~/.claude/ 目录结构,知道 CLAUDE.md 是干什么的。grep、tail、wc)。很多人第一次接触技能,会把它和 CLAUDE.md、自定义斜杠命令混淆。动手前花两分钟理清边界,能避免后面反复返工。
Claude Code 官方对这三者的定位非常明确:
| 载体 | 何时加载 | 适合放什么 |
|---|---|---|
CLAUDE.md |
每次会话全量注入上下文 | 事实、约定、项目说明("是什么") |
| 斜杠命令/技能 | 仅在调用时按需加载 | 流程、清单、多步操作("怎么做") |
官方原话很关键:"当一个 CLAUDE.md 的章节已经长成了一套操作流程,而不是一条事实时,就该把它抽成技能。" 因为技能的正文只有在被调用时才进上下文,长篇参考资料在用不到时几乎零成本。
而且,自定义命令(.claude/commands/deploy.md)已经并入技能体系——它和 .claude/skills/deploy/SKILL.md 产生的都是 /deploy,行为一致。技能只是多了:支持目录放辅助文件、用 frontmatter 控制谁能触发、能被 Claude 自动按需加载。所以新需求一律用技能,别再用旧的 commands 目录。
我们的目标场景:每次线上告警,你都要把日志丢给 Claude,让它挑出报错、归类、猜根因、给修复建议。这套固定流程重复了不下十次——典型的技能候选。
Claude Code 有四个加载层级,优先级从高到低:
| 层级 | 路径 | 作用范围 |
|---|---|---|
| Enterprise(托管) | managed settings | 全组织 |
| Personal(个人) | ~/.claude/skills/<name>/SKILL.md |
你所有项目 |
| Project(项目) | .claude/skills/<name>/SKILL.md |
仅当前项目 |
| Plugin(插件) | <plugin>/skills/<name>/SKILL.md |
启用该插件处 |
同名技能,enterprise 覆盖 personal,personal 覆盖 project。日志诊断这种个人通用工具,放 Personal 最合适。先建目录:
mkdir -p ~/.claude/skills/diag-log
常见问题:项目级技能里的
allowed-tools不会立刻生效——必须先接受工作区信任对话框(workspace trust)。所以别人仓库里检入的技能,信任前一定要先读一遍 SKILL.md,因为技能可以给自己授予很宽的工具权限。个人技能无此门槛。
每个技能必须有 SKILL.md,分两部分:--- 之间的 YAML frontmatter 告诉 Claude 何时用它,下面的 markdown 是 Claude 被触发后遵循的指令。先把最简版写到 ~/.claude/skills/diag-log/SKILL.md:
---
name: diag-log
description: Diagnose the root cause of errors in an application log file. Use when the user pastes a stack trace, points at a .log file, or asks "why is it failing" or "what's the root cause".
---
## 任务
分析用户提供的日志,定位最可能的根因。
## 步骤
1. 用 Read/Grep 读取日志,提取所有 ERROR/Exception/FATAL/panic 行。
2. 按错误签名聚类,统计出现次数。
3. 取出现最频繁的一类,结合上下文推断根因。
4. 给出 1 条最可能的根因假设 + 2 条验证步骤。
## 输出格式
- 根因假设(一句话)
- 关键证据(3 条以内,带行号引用)
- 验证步骤
预期结果:保存后无需重启——Claude Code 会实时监控技能目录的文件变化(仅对 SKILL.md 文本生效)。在一个 git 项目里 claude 进会话,造一段测试日志:
cat > /tmp/app.log <<'EOF'
2026-06-30 09:01:12 INFO server started
2026-06-30 09:02:33 ERROR NullReferenceException at UserService.java:142
2026-06-30 09:02:33 ERROR NullReferenceException at UserService.java:142
2026-06-30 09:05:11 FATAL ConnectionPool exhausted, refusing new connections
2026-06-30 09:05:12 ERROR NullReferenceException at UserService.java:142
EOF
然后两种方式测试。让 Claude 自动触发——直接问:"帮我看看 /tmp/app.log 为什么报错"。或手动触发——输入 /diag-log。两种方式都应该返回结构化的根因分析。
常见问题:技能不触发。 八成是 description 太含糊。Claude 决定要不要自动加载,靠的就是读 description。公式是:"动词短语说清做什么。Use when 触发词1、触发词2。" 把"用户会怎么说"的关键词塞进去。如果还是不行,在会话里问 "What skills are available?" 确认它出现在列表里;frontmatter YAML 写错会被静默忽略元数据,加
--debug启动能看到解析错误。
最小版有个隐患:它会被 Claude 自动触发。想象你随口问"日志该怎么排障",Claude 可能误判去跑你的技能。日志分析虽然读多写少,但你希望"指定文件才跑、且能传文件名进去"。加三个字段:
---
name: diag-log
description: Diagnose the root cause of errors in an application log file. Use when the user pastes a stack trace, points at a .log file, or asks "why is it failing" or "what's the root cause".
argument-hint: <path-to-log-file>
arguments:
- log_file
disable-model-invocation: true
---
## 任务
诊断 `$log_file` 中错误的根因。
...(步骤同上)
逐个解释:
disable-model-invocation: true:安全锁。设了之后只有你能用 /diag-log 手动触发,Claude 不会自作主张加载它。官方建议:凡是有副作用(部署、提交、发邮件、调外部 API)的技能默认都加这个。它会顺带把技能的 description 从上下文里移除,省 token。arguments: [log_file]:声明命名位置参数。这样在正文里就能用 $log_file 引用,比裸 $ARGUMENTS 更清晰。运行 /diag-log /tmp/app.log 时,$log_file 自动展开成 /tmp/app.log。argument-hint:纯体验优化。打 /diag-log 时自动补全会显示 /diag-log <path-to-log-file>,告诉使用者要传什么。<> 表示必填、[] 表示可选,沿用 man 手册惯例。这两个布尔字段是新人最容易搞混的一对,画张表记牢:
| frontmatter 设置 | 你能调用 | Claude 能自动调用 | description 是否在上下文 |
|---|---|---|---|
| 默认 | 能 | 能 | 在;调用时加载全文 |
disable-model-invocation: true |
能 | 不能 | 不在;你调用时才加载 |
user-invocable: false |
不能 | 能 | 在;调用时加载 |
常见问题:两个都设了技能就消失了。
disable-model-invocation: true+user-invocable: false同时设,等于谁都不能触发,技能彻底不可达——别这么干。
现在技能跑起来,Claude 每读一次日志、每 grep 一次都要弹权限确认,流畅度很差。用 allowed-tools 把这些工具预先放行:
allowed-tools:
- Read
- Grep
- Bash(grep:*)
- Bash(awk:*)
- Bash(tail:*)
- Bash(wc:*)
关键认知:allowed-tools 是"免确认",不是"限制"。 它让列出的工具在技能激活期间免提示,但其他工具照样能用,仍受你全局权限规则约束。语法和 settings.json 的 permissions.allow 一致,所以 Bash(grep:*) 这种带前缀的细粒度规则也支持。
另一个字段 disallowed-tools 才是真正的"限制"——它在技能激活期间把指定工具从 Claude 可用池里移除,比如给一个后台循环技能配 disallowed-tools: [AskUserQuestion] 让它绝不打断你。注意:这个限制在你发下一条消息时就清除了,是临时的。
常见问题:写了 allowed-tools 还是弹窗。 两种可能:一是项目级技能没接受工作区信任;二是权限太宽(如裸
Bash)被全局 deny 规则挡住——全局 deny 永远赢过 allowed-tools,这是安全特性。原则:能窄则窄,Bash(git log:*)而不是Bash。
这是技能体系里最趁手的特性之一。在正文里写 !命令`(行首或空格后的!`),Claude Code 会在把技能正文发给 Claude 之前先执行它,把输出原地替换进去。等于让指令"自带实测数据"。
官方的 summarize-changes 示例就是用 !git diff HEAD` 把当前 diff 拉进提示词,这样 Claude 看到的是真实工作区,而不是靠猜。我们给diag-log` 加上:调用时自动把日志里的报错行和体量统计注入进来。
## 日志实测信号
```!
echo "## 文件体量"
wc -l "$log_file" 2>/dev/null
echo
echo "## 头尾时间戳(判断故障窗口)"
head -n 1 "$log_file" 2>/dev/null
tail -n 1 "$log_file" 2>/dev/null
echo
echo "## 报错签名(按频次)"
grep -nE 'ERROR|Exception|FATAL|panic|Traceback' "$log_file" 2>/dev/null \
| sort | uniq -c | sort -rn | head -n 10
上面的信号已从真实日志实时抽取。基于它们定位 $log_file 的根因……
**工作原理**:参数替换(`$log_file` → 实际路径)和命令注入(`!`块执行)在**同一次对原文的替换扫描**中完成;命令输出作为纯文本插入,不会被二次扫描,所以一个命令不能"吐出"另一个 `!` 占位符。Claude 最终看到的是已经填好数据的完整提示。
行内写法 `!`cmd`` 仅在 `!` 位于行首或紧跟空格时才被识别;`KEY=!`cmd`` 这种紧跟字符的写法会被当字面量。多行命令用 ` ```! ` 围栏。企业可以用 `"disableSkillShellExecution": true` 设置在托管层把这套机制关掉(仅对用户/项目/插件来源生效,bundled 和 managed 技能不受影响)。
> **常见问题:注入命令失败导致输出难看。** 注入的命令每次调用都跑,失败的命令会往提示词里灌噪声,模型可能误读。务必加 `2>/dev/null` 和 `|| echo "(无匹配)"` 兜底。
## 第七步(可选):大日志用 fork 隔离上下文
如果日志几十 MB、要读上百个文件做交叉分析,`context: fork` 能让技能在一个**全新子代理上下文**里跑,不污染主会话:
```yaml
---
name: diag-log-deep
description: Deep multi-log root-cause analysis for large incidents. ...
context: fork
agent: Explore
allowed-tools:
- Grep
- Glob
- Read
---
对比 $ARGUMENTS 指定的多个日志,交叉定位故障起源……
context: fork 时,正文成为驱动子代理的 prompt,它看不到你的对话历史。所以 fork 只适合"任务明确的技能"——若是纯约定类技能("遵循这些 API 风格"),子代理收到约定却没有可执行任务,会空手而归。agent 字段决定执行环境:Explore(只读搜索,便宜)、Plan(架构设计)、general-purpose(可写,贵)。错配会静默失败,务必让 agent 类型匹配技能的职责。
看到技能被触发,只说明 Claude 找到了它,不代表它干对了。官方强调要分开测两件事:该触发的 prompt 是否触发了;触发了输出是否符合预期。方法是基线对照——收集几个真实 prompt,每个都在全新会话里分别跑"有技能"和"禁用技能"两种,对比结果。全新会话很关键,因为你写技能时残留的上下文会掩盖指令的漏洞。
手动迭代太累时,用官方的 skill-creator 插件自动化这套循环:
/plugin install skill-creator@claude-plugins-official
/reload-plugins
装好后让它 evaluate my diag-log skill with skill-creator,它会:写测试用例存进 evals/evals.json、每个用例起一个隔离子代理跑、按断言打分写 grading.json、把"有/无技能"的通过率/耗时/token 汇总成 benchmark.json,还能跑两版技能的盲测 A/B、自动生成 should/should-not 触发 prompt 来调 description。
当技能太多导致描述被截断而漏触发时,跑 /doctor 看哪些技能描述被缩短了;技能列表的字符预算默认是模型上下文窗口的 1%,可用 skillListingBudgetFraction(如 0.02)调高,或把低优先级技能在 skillOverrides 里设成 "name-only" 省描述。单条 description + when_to_use 上限 1536 字符,把最关键的用例放最前面。
把前面几步拼起来,得到一个可直接用的生产级技能。目录结构:
~/.claude/skills/diag-log/
├── SKILL.md # 主指令(必需)
└── scripts/
└── extract.sh # 可选:把重统计逻辑固化成脚本
SKILL.md:
---
name: diag-log
description: Diagnose the root cause of errors in an application log file. Use when the user pastes a stack trace, points at a .log file, or asks "why is it failing", "what's the root cause", or "分析日志/排查报错".
argument-hint: <path-to-log-file>
arguments:
- log_file
disable-model-invocation: true
allowed-tools:
- Read
- Grep
- Bash(grep:*)
- Bash(awk:*)
- Bash(tail:*)
- Bash(wc:*)
- Bash(bash:*)
---
## 实时信号(从 $log_file 抽取)
```!
echo "## 体量"; wc -l "$log_file" 2>/dev/null
echo "## 时间窗口"; head -n1 "$log_file" 2>/dev/null; tail -n1 "$log_file" 2>/dev/null
echo "## 报错签名 Top10"
grep -nE 'ERROR|Exception|FATAL|panic|Traceback' "$log_file" 2>/dev/null \
| sort | uniq -c | sort -rn | head -n10 || echo "(未匹配到错误模式)"
基于上面的实时信号,对 $log_file 做根因诊断。若需要更细的行级上下文,再调用 Grep/Read;也可运行预置统计脚本:
bash ${CLAUDE_SKILL_DIR}/scripts/extract.sh "$log_file"
`scripts/extract.sh`(把统计逻辑固化,避免 Claude 每次重写命令):
```bash
#!/usr/bin/env bash
# 用法: extract.sh <logfile>
set -euo pipefail
log="${1:?用法: extract.sh <logfile>}"
[ -f "$log" ] || { echo "文件不存在: $log"; exit 1; }
echo "== 各级别计数 =="
grep -oE '(ERROR|WARN|FATAL|Exception|panic)' "$log" 2>/dev/null \
| sort | uniq -c | sort -rn || true
echo; echo "== 异常栈头(去重)=="
grep -E 'Exception|Error|panic' "$log" 2>/dev/null \
| sed -E 's/^[0-9: -]+//' | sort -u | head -n 20 || true
echo; echo "== 每分钟错误密度 =="
grep -E 'ERROR|FATAL' "$log" 2>/dev/null \
| awk '{print substr($1,1,10)"T"substr($2,1,5)}' \
| sort | uniq -c | sort -rn | head -n 10 || true
跑 /diag-log /tmp/app.log,预期输出形如:
根因假设(置信度:高):UserService.java:142 的 NullReferenceException 反复触发,
疑似某用户记录字段为 null 未做空值判断,错误风暴耗尽连接池引发 FATAL。
关键证据:
- L2/L3/L5 三次同一行同一异常(/tmp/app.log:142)
- L4 ConnectionPool exhausted 紧随其后
验证步骤:
1. grep -n 'userId' UserService.java:142 附近定位入参
2. 查最近发布记录是否有该行改动
3. 在测试环境用 null 入参复现
${CLAUDE_SKILL_DIR} 会展开成技能所在目录,无论技能装在个人级、项目级还是插件级都能正确解析——这是技能"可移植"的关键。
下面的流程图把"从需求到可用技能"的决策链串起来,遇到新需求时照着走一遍:
flowchart TD
A[重复的固定流程] --> B{是事实还是流程?}
B -->|事实/约定| C[放进 CLAUDE.md]
B -->|多步操作/清单| D[做成 Skill]
D --> E[选层级]
E -->|个人通用| F[~/.claude/skills]
E -->|团队共享| G[.claude/skills 检入git]
E -->|跨项目分发| H[打包成 Plugin]
F --> I[写 SKILL.md]
G --> I
H --> I
I --> J{有副作用?}
J -->|是| K[disable-model-invocation: true<br/>只手动触发]
J -->|否| L{要免确认?}
L -->|是| M[加 allowed-tools]
L -->|否| N[保持默认自动触发]
K --> O[需要实时数据?]
M --> O
N --> O
O -->|是| P[用 !cmd 动态注入]
O -->|否| Q[发布]
P --> Q
Q --> R[skill-creator 跑 eval]
R --> S{通过率达标?}
S -->|否| I
S -->|是| T[上线]
技能被调用后的上下文生命周期也值得记住,决定你怎么写指令:
sequenceDiagram
participant U as 用户/Claude
participant CC as Claude Code
participant S as SKILL.md
participant M as 模型
U->>CC: 输入 prompt 或 /diag-log file
CC->>CC: 参数替换 ($log_file) + !cmd 注入
CC->>S: 渲染正文(数据已注入)
S->>M: 作为单条消息进入对话
Note over M: 正文此后常驻该会话
M->>M: 后续轮次不再重读文件
Note over CC: 自动压缩时<br/>每个技能保留前 5000 token<br/>合计预算 25000 token<br/>从最近调用的开始保留
核心含义:技能正文是常驻指令而非一次性步骤。压缩后会按"最近优先"重新挂载,所以把最重要的指令写在正文靠前位置。
| 现象 | 原因 | 处理 |
|---|---|---|
| 技能完全不触发 | description 太含糊,或 YAML 格式错被静默忽略 | 加触发词;--debug 启动看解析错误 |
| 技能乱触发 | description 过宽 | 收窄 description,或设 disable-model-invocation: true 只手动触发 |
| 描述被截断导致漏触发 | 技能太多超出字符预算 | /doctor 查;调高 skillListingBudgetFraction;低优先级设 "name-only" |
| allowed-tools 仍弹窗 | 项目级未接受信任,或被全局 deny 挡住 | 接受信任;记住全局 deny 永远赢 |
| 改了 hooks/scripts 不生效 | 实时检测只覆盖 SKILL.md 文本 | /reload-plugins 重载 |
| 嵌套技能同名 | monorepo 多包都有 deploy | 用目录限定名 /apps/web:deploy |
evals.json 格式与迭代工作流,配合 skill-creator 插件做量化评测。context: fork + agent 的子代理执行模型,理解技能如何在隔离上下文里跑。context: fork 版做跨日志关联;或加 paths: ["**/*.log"] + user-invocable: false,做成"一编辑日志就自动加载诊断约定"的沉默情境技能。[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
