跳到正文
DEV Community· Gabriel Anhaia·· 3 小時前AI 評分65

Claude Code Skill 逐步教學:建立只在需要時載入的 SKILL.md

Write a Claude Code Skill That Loads Only When It's Needed: SKILL.md Step by Step

AI 導讀

文章示範如何為 Claude Code 建立只在相關任務觸發時載入的 release-notes Skill,並以 SKILL.md 串連提交腳本和風格參考。Skill 名稱和描述在啟動時約佔每個技能 100 tokens,正文觸發時載入且低於 5k tokens;腳本執行後只有輸出進入上下文。文中亦提供目錄配置、YAML 描述寫法,以及用應觸發與不應觸發的提示詞測試描述範圍的方法。

正文

Open your CLAUDE.md and scroll. Somewhere past the build commands there is a section called "Cutting a release": how to find the last tag, which commit types to drop, the heading order, the tone. Forty lines you need maybe twice a month. They sit in the context window for every turn of every session, including the afternoon you spend fixing a CSS bug.

That is how CLAUDE.md works by design. Claude Code reads it at the start of every session, and the same page tells you to target under 200 lines per file and to move "a multi-step procedure" into a skill instead. A skill's body stays on disk until Claude decides the task needs it. The only part in context up front is a name and a one-paragraph description.

Move that procedure into a Claude Code skill: a SKILL.md, a bundled script, and a style reference that loads only when the notes get written.

What each piece costs, and when

Anthropic's Agent Skills overview splits a skill into three levels, each loaded at a different time:

Level When it loads Cost per the docs
name + description Always, at startup About 100 tokens per skill
SKILL.md body When the skill triggers Under 5k tokens
Bundled files and scripts When read or run Nothing until accessed

Scripts get an extra discount. When Claude runs a bundled script, only its output enters context, not the code. So the deterministic work (listing commits, grouping them) belongs in a script, and the judgment (which commits a user cares about) belongs in markdown.

One caveat before you move everything out of CLAUDE.md. Once a skill is invoked, its content stays in the conversation for the rest of the session, and Claude Code does not re-read the file on later turns. So the saving is in the sessions that never touch releases. In the release session itself, the body sits in context from the moment the skill fires.

Three tiers of a filing system: an index card always on the desk, a folder opened on demand, and a closed drawer of reference files
Only the index card is always on the desk. The folder and the drawer open when the task needs them.

Build the release-notes skill

Project skills live in .claude/skills/<name>/SKILL.md, personal ones in ~/.claude/skills/<name>/SKILL.md (skills docs). Use the project location so the skill ships with the repo:

.claude/skills/release-notes/
├── SKILL.md
├── reference/
│   └── style.md
└── scripts/
    └── commits.sh

Start with the script. It finds the last tag (or takes one as an argument), lists non-merge commits since then, and tags each line with its Conventional Commits type so the model does not have to parse prefixes:

#!/usr/bin/env bash
# Usage: commits.sh [from-tag]
# Prints "<type>\t<hash> <subject>" for each commit
# since the tag, grouped by type.
set -euo pipefail

from="${1:-}"
if [ -z "$from" ]; then
  from="$(git describe --tags --abbrev=0 2>/dev/null \
    || true)"
fi

if [ -n "$from" ]; then
  echo "# commits since $from"
  range="$from..HEAD"
else
  echo "# no tag found, listing all commits"
  range="HEAD"
fi

git log "$range" --no-merges --pretty='format:%h %s' |
  awk '
    { type = "other" }
    $2 ~ /^feat(\(.*\))?!?:/ { type = "feat" }
    $2 ~ /^fix(\(.*\))?!?:/  { type = "fix" }
    { print type "\t" $0 }
  ' | sort -s -k1,1

git describe --tags --abbrev=0 prints only the closest tag reachable from HEAD, lightweight tags included. The || true keeps set -e from killing the script in a repo with no tags yet.

Now the style reference. This is the forty lines that used to live in CLAUDE.md, cut down to what the model cannot guess:

# Release notes style

## Headings
Use these, in this order, and skip empty ones:
### Features, ### Fixes, ### Other

## Bullets
- Start with a past-tense verb: "Added", "Fixed".
- Describe the effect for the user, not the code.
- End with the short hash in parentheses: (a1b2c3d).
- A `!` right before the colon marks a breaking change.
  List it first under its heading, prefixed **Breaking:**.

## Leave out
- Refactors with no visible effect.
- Dependency bumps, unless they fix a security issue.

Then SKILL.md, which ties the two together:

---
name: release-notes
description: >-
  Drafts user-facing release notes from the git commits
  since the last tag, grouped into Features, Fixes and
  Other. Use when the user asks for release notes, a
  changelog entry, what shipped since a version, or a
  summary of changes for a release. Not for single
  commit messages or PR descriptions.
---

# Release notes

1. Get the commits by running:
   `bash "${CLAUDE_SKILL_DIR}/scripts/commits.sh" [tag]`
   Pass a tag only if the user named one.
2. Read [reference/style.md](reference/style.md)
   before writing anything.
3. One bullet per user-visible change. Merge commits
   that describe the same change. Drop chore, ci, test
   and docs commits unless the user asks for them.
4. Reply with Markdown only. Edit CHANGELOG.md only
   if the user asks you to.

The >- is plain YAML: it folds the indented lines into one string, so a long description stays readable and a stray colon inside it cannot break the parse.

${CLAUDE_SKILL_DIR} is a substitution Claude Code fills in with the skill's own directory, so the script path works from any working directory. The name must be lowercase letters, numbers and hyphens, and on Anthropic's other surfaces it cannot contain "anthropic" or "claude". The reference file is linked directly from SKILL.md, one level deep, because the best-practices guide warns that Claude may only partially read files reached through a chain of references.

You could also inline the script with the skills docs' inline shell syntax (an exclamation mark followed by a backticked command), which runs before Claude sees the skill and pastes the output in. The docs note that a failing command aborts the whole invocation, and the tag argument here is optional, so letting Claude run the script is the simpler choice.

Claude Code watches skill directories and picks up new or edited skills mid-session. If .claude/skills/ did not exist when the session started, Claude Code is not watching it yet: run /reload-skills to pick the skill up, and run it again after each later edit in that session, or start a new session.

Test that it fires, and that it stays quiet

Start a fresh session in a repo that has at least one tag. First ask What skills are available?, one of the checks in the troubleshooting guide. release-notes should be in the list. If it shows up but never triggers, look at the YAML: when frontmatter fails to parse, the skill still loads with empty metadata, so /release-notes works but Claude has no description to match. Run claude --debug to see the parse error. One easy way to cause it: an unquoted one-line description containing a colon followed by a space.

Then run prompts on both sides of the line:

Prompt Should the skill load?
Write release notes for everything since v1.4.0 Yes
What shipped since the last tag? Yes
Draft a CHANGELOG entry for this release Yes
Write a commit message for my staged changes No
Explain what git describe does No

The "No" rows are the useful ones. They share vocabulary with the skill (commits, git, tags) and should still be handled without it. If the commit-message prompt pulls the skill in, the description is too broad.

To see what the skill changes, get a baseline. Turn it off in .claude/settings.local.json with skillOverrides, run the same prompts in a new session, then remove the override:

{
  "skillOverrides": {
    "release-notes": "off"
  }
}

For cost, the Skills row in /context reports the size of the skill listing after the budget is applied, so it matches what the model receives, and /skill-doctor (Claude Code v2.1.252 or later) reports each skill's context cost and invocation count. Five prompts by hand catch the obvious misses. To test more than five, the docs' skill-creator plugin generates should-trigger and should-not-trigger prompts, measures the hit rate and proposes description edits. Install it with /plugin install skill-creator@claude-plugins-official.

A doorman at a club entrance reading a small card, letting one figure through and turning another away
The description is the only thing checked at the door. The body never gets a vote on whether it loads.

The description is the real prompt

Everything in SKILL.md below the frontmatter is invisible until the skill is chosen. The choice is made from the description alone, which Anthropic's overview says Claude "matches your request against". A great body behind a vague description never runs.

Say what and when. The best-practices guide asks for both: what the skill does, and the triggers for using it. "Helps with releases" fails both halves. "Drafts release notes from commits since the last tag. Use when the user asks for release notes, a changelog entry..." gives the model a task and a set of phrases to match.

Write in third person. The description is injected into the system prompt, and the guide warns that "I can help you" or "You can use this" can cause discovery problems. "Drafts release notes" reads correctly from any point of view.

Use the words your users type. Nobody asks for "a release artifact summary". They ask "what shipped since 1.4" or "write the changelog". Put those phrases in. The docs' first troubleshooting step for a skill that does not trigger is to check that the description "includes keywords users would naturally say".

Put the key use case first. In Claude Code the combined description and when_to_use text is cut at 1,536 characters in the listing by default. Anything past that never reaches the model, so front-load the sentence that matters.

The whole listing also has a budget, 1% of the model's context window by default. If you have many skills and the listing overflows it, Claude Code drops whole descriptions, starting with the skills you invoke least. A new skill starts at zero invocations, so it is an early candidate. Check the Skills row in /context, and set low-priority skills to "name-only" in skillOverrides to free room.

Name the boundary. The "Not for single commit messages or PR descriptions" line is a design choice of this tutorial rather than a rule from the docs. It earns its place when a nearby request keeps triggering the skill. The documented fix for over-triggering is to make the description more specific, and an explicit exclusion is one way to do that.

Stay inside the portable limits. On the Claude API and claude.ai, the description must be non-empty, at most 1,024 characters and free of XML tags. Claude Code's own docs only mention the 1,536-character listing cut, but staying under 1,024 keeps the skill portable to those surfaces.

When a skill is the wrong tool

If you only ever want release notes when you ask for them, add disable-model-invocation: true to the frontmatter. The description then leaves context entirely, and the skill runs only when you type /release-notes. That is the cheapest option, and the right one for anything with side effects you want to trigger by hand.

If a rule has to hold every time, such as "never push a tag without running the tests", a skill is the wrong home. The docs recommend a hook for that, because a hook runs on its event whether or not Claude is following any skill. CLAUDE.md stays the place for facts every session needs: build commands, layout, conventions. Everything else can wait on disk until a request asks for it.


If this was useful

A skill description is a prompt with one job, getting picked for the right request, and that is the kind of prompt the Prompt Engineering Pocket Guide spends its time on: being specific, choosing the words the model will match, and testing the misses as well as the hits. If you are trimming a CLAUDE.md that has grown into a manual, the same habits apply.

Prompt Engineering Pocket Guide: Techniques for Getting the Most from LLMs

來源:DEV Community · dev.to