TL;DR i werdykt
Skill w Claude Code może działać bez zarzutu, gdy wpiszesz jego nazwę, a mimo to nigdy się nie załadować, gdy poprosisz zwykłymi słowami. Zanim Claude zdecyduje się załadować skill, czyta jedną linię na skill: nazwę i opis. Dopasowuje tę linię do twoich słów. Jeśli linia nie używa twoich słów, skill milczy.
W naszym uruchomieniu skill do release notes z opisem "Generates changelog documents from repository history." załadował się w 0 z 5 świeżych sesji przy prośbie "what did we ship this week?". Przepisaliśmy tę jedną linię tak, by mówiła, co skill robi, kiedy go użyć i jakich słów używamy. Ta sama prośba załadowała go wtedy w 5 z 5.
Werdykt:
- Skill się opłaca przy zadaniu, które powtarzasz ze stałym wynikiem: cotygodniowe notatki, opis pull requesta, checklista wydania.
- Nie opłaca się przy jednorazowym pytaniu i nie jest narzędziem do reguły, która musi zawsze obowiązywać. To zadanie dla hooka.
- Stos skilli, których nikt nie używa, kosztuje cię, bo po przekroczeniu budżetu listy Claude Code ucina opisy, najrzadziej używane najpierw.
- Jedna zasada: pisz opis słowami, których naprawdę używasz, gdy prosisz o to zadanie.
Zestaw poniżej zawiera nasz plik skilla przed i po, szablon opisu, checklistę dla świeżej sesji i test dwudziestu próśb, którego użyliśmy.
Co mówią dokumentacja i praktyka
Gdzie mieszka skill. Skill to folder z plikiem SKILL.md w środku, albo w ~/.claude/skills/<skill-name>/ dla każdego projektu, albo w .claude/skills/<skill-name>/, żeby dostał go też twój zespół. Luźny plik name.md się nie ładuje. s1 s2
Claude czyta opis, nie treść. Lista nazw i opisów jest w kontekście w każdej turze, a treść ładuje się dopiero po wywołaniu skilla. Dokumentacja mówi, że to opis służy Claude do decyzji, kiedy zastosować skill. s1
Lista ma limit i budżet. Tekst opisu i when_to_use jest razem ucinany przy 1536 znakach. Cała lista jest ograniczona do 1% okna kontekstu, a gdy się przepełni, Claude Code porzuca opisy, zaczynając od skilli, które wywołujesz najrzadziej. Dokumentacja mówi, że to usuwa słowa kluczowe potrzebne Claude do dopasowania twojej prośby. Ostrzeżenie zobaczysz w logu debugowania z --debug. s1
Zepsuty nagłówek to pułapka, która wygląda jak sukces. Jeśli YAML jest błędny, treść ładuje się z pustymi metadanymi, więc /skill-name nadal działa, ale Claude nie może dopasować twojego opisu. Uruchom z --debug, by zobaczyć błąd parsowania, albo uruchom claude plugin validate .claude/skills. s1 s2
Sprawdź odznakę. Jeśli skill pojawia się w /skills, ale Claude nigdy go nie wywołuje, to albo ustawiono disable-model-invocation: true, albo opis nie pasuje do tego, jak formułujesz prośbę. Etykieta user-only oznacza, że Claude nie uruchomi go sam. s2
Co zawiera dobry opis. Przewodnik Anthropic mówi, że opis powinien zawierać zarówno to, co skill robi, jak i kiedy go użyć, napisany w trzeciej osobie, z kluczowymi terminami, które wpisują użytkownicy. Zaznacza, że Claude używa go do wyboru właściwego skilla spośród potencjalnie ponad 100. s3
Claude ma skłonność do niedowyzwalania. Skill-creator od Anthropic mówi "currently Claude has a tendency to 'undertrigger' skills" i zaleca nieco "pushy" opis. s18 Jego test wyzwalania używa 20 zapytań, mieszanki takich, które mają wyzwolić, i takich, które nie mają, przy czym bliskie pudła są najcenniejsze. Pętla dzieli je na 60% treningowych i 40% zatrzymanych do testu, uruchamia każde zapytanie 3 razy i zachowuje opis z najlepszym wynikiem testowym. s4 Skrypt ewaluacji uruchamia każde zapytanie przez claude -p i sprawdza, czy skill został użyty. s19
Aktywacja zachowuje się jak dopasowywanie słów kluczowych. Na prawdziwym pliku binarnym jeden zewnętrzny test (22 prompty, Sonnet 4.5) zmierzył punkt odniesienia 55% i 50% w dwóch uruchomieniach oraz hook wymuszonej oceny na poziomie 100%. Prompt z jawnym tokenem aktywował się niemal w 100%, a pośrednie sformułowania oscylowały blisko 0%. Wniosek autora: bliżej dopasowania słów kluczowych niż semantycznego. To jeden blog, starszy model i inne repo, więc traktuj to jako dowód wspierający, nie regułę. s5
Opis to wyzwalacz, nie streszczenie. Audyt dziewięciu prawdziwych skilli doszedł do tego samego wniosku: opis wymieniający konkretne sytuacje wygrywa z takim, który mówi, czym skill jest. s8
Istnieje też odwrotna wada. Jeden programista zobaczył, jak skill wyzwala się przy złych zadaniach, napisał ewaluacje wyzwalania, przepisał opis jako "Load when..." i zauważył, że skill wart zachowania rodzi się zwykle z porażki, której masz dość oglądać dwa razy. s11
Inni też na to trafili. Zgłoszenie "Claude Code not aware of skills in .claude/skills" ma 75 reakcji i było nadal otwarte 2026-10-17. Obejścia w wątku to wskazówka w CLAUDE.md i hook. s7
Gdzie hook bije skill. Najwyżej oceniana odpowiedź w wątku z 778 głosami mówi, że skill to "basically just a prompt you save for later", który Claude może wybrać albo ty wywołujesz poleceniem z ukośnikiem, a hook jest na sytuację, gdy skille są zbyt luźne i chcesz determinizmu. s9 Inny wątek ostrzega, że przypomnienia mogą słabnąć w długiej sesji. s10
Zestaw
Skill przed i po. Różni się tylko linia opisu, treść jest identyczna.
release-notes.before.SKILL.md
---
name: release-notes
description: Generates changelog documents from repository history.
---
# Release notes
1. Find the range: commits since last Monday (`git log --since="last monday" --no-merges --pretty="%h %s"`).
2. Drop `chore` and `docs` commits.
3. Group what is left by the conventional-commit scope (router, css, jsx, serve-static...), fixes and features apart.
4. Write one line per change, in plain English, present tense, no commit hashes, with the PR number at the end.
5. Output a markdown block titled "Release notes, week of <date>", with a two-line summary on top.
release-notes.before.SKILL.md on GitHub
release-notes.after.SKILL.md
---
name: release-notes
description: Writes this week's release notes from git log, grouped by scope (router, css, jsx). Use when the user asks what we shipped, what changed this week, for release notes, a changelog, or a summary of recent commits.
---
# Release notes
1. Find the range: commits since last Monday (`git log --since="last monday" --no-merges --pretty="%h %s"`).
2. Drop `chore` and `docs` commits.
3. Group what is left by the conventional-commit scope (router, css, jsx, serve-static...), fixes and features apart.
4. Write one line per change, in plain English, present tense, no commit hashes, with the PR number at the end.
5. Output a markdown block titled "Release notes, week of <date>", with a two-line summary on top.
release-notes.after.SKILL.md on GitHub
Szablon opisu, z naszym prawdziwym opisem jako przykładem.
description-template.md
# Skill description template
The description is the one line Claude reads before it decides to load your skill. Write it for the moment you ask, not for the file.
## Template
```
<Verb in third person> <what the skill produces>, <how it is shaped>. Use when the user asks <phrase 1>, <phrase 2>, <phrase 3>, or <phrase 4>.
```
Three parts, in this order:
1. What it does. Third person ("Writes", never "I can" or "You can use this"). Name the output.
2. When to use it. Start with "Use when the user asks".
3. Your words. The exact phrases you or your team type, copied from your own history, not synonyms you invented.
Put the key use case first. The description and `when_to_use` are cut together at 1,536 characters in the skill listing.
## Worked example (the real one from our run)
Before, a summary of the skill:
```
Generates changelog documents from repository history.
```
After, what + when + the words we type:
```
Writes this week's release notes from git log, grouped by scope (router, css, jsx). Use when the user asks what we shipped, what changed this week, for release notes, a changelog, or a summary of recent commits.
```
What changed:
- What: "Writes this week's release notes from git log, grouped by scope".
- When: "Use when the user asks ...".
- Your words: "what we shipped", "what changed this week", "release notes", "a changelog", "a summary of recent commits".
## Fill-in sheet
- Skill name:
- The prompt I keep retyping (copy it from your history):
- The output I want from it:
- Verb + output (what it does):
- Four phrases I really type (when):
- Phrases that sound close but are a different job (for your near misses):
## Check before you save
- Third person, no "I" and no "you".
- At least three phrases you really type.
- Under 1,536 characters in total.
- Not so wide that it matches a neighbour job. Add the near misses to your trigger test.
description-template.md on GitHub
Checklista dla świeżej sesji.
trigger-checklist.md
# Fresh-session trigger checklist
A skill that runs by name can still be invisible to a plain request. Test it the way a teammate will use it.
## Test in a fresh session
1. Open a new session. A skill already used stays in the conversation, so a test in the same session proves nothing.
2. Ask for the task in your own words, without the skill's name. Example: "what did we ship this week?"
3. Watch for the Skill tool call before Claude touches anything else. If Claude runs the commands itself and writes its own summary, the skill was skipped.
4. Repeat in several fresh sessions. A handful of runs is a demo, not a statistic.
## If it was skipped, read why before you edit
1. `/skills`: is it listed, is it switched on, is there a `user-only` badge? A `user-only` badge means Claude will not trigger it on its own.
2. Ask "What skills are available?" and check your skill is in the answer.
3. `claude --debug`, then search the log for the skills line (how many loaded) and for a "Skill listing over budget" warning. Over budget means some descriptions are cut, the least invoked first.
4. `claude plugin validate .claude/skills`: a malformed header still lets `/name` work but leaves Claude with empty metadata to match against.
5. Check the file is `.claude/skills/<name>/SKILL.md`, not a loose `<name>.md`, and that the header starts on the first line.
## Fix, then prove it
1. Rewrite the description with `description-template.md`.
2. Fresh session, same plain request, several runs: does the Skill call come first now?
3. Baseline: set the skill to `"off"` in `skillOverrides` (saved to `.claude/settings.local.json` from `/skills`), ask again, then switch it back on.
## Hold it
1. Write twenty requests: ten that should load the skill, ten near misses (`evals.json` is our set).
2. Run each in its own fresh session and note whether the skill loaded (`trigger_test.py`, `tally.py`).
3. Re-run the list every time you change the description.
4. For a skill that must never miss: call it by name, or add a UserPromptSubmit hook (`hook.json`).
trigger-checklist.md on GitHub
Zestaw testowy dwudziestu próśb: dziesięć próśb, które powinny załadować skill, dziesięć bliskich pudeł, które nie powinny. Zmień zapytania, by pasowały do twojego repozytorium.
evals.json
[
{"query": "what did we ship this week?", "should_trigger": true},
{"query": "can you put together the release notes for this week", "should_trigger": true},
{"query": "what changed in the repo since monday", "should_trigger": true},
{"query": "write the changelog for the commits we merged this week", "should_trigger": true},
{"query": "I need a summary of this week's commits for the team update on friday", "should_trigger": true},
{"query": "draft the weekly notes of what got fixed, grouped by area", "should_trigger": true},
{"query": "list what landed this week, router and css changes apart", "should_trigger": true},
{"query": "give me something to post in the team channel about this week's fixes", "should_trigger": true},
{"query": "recap the work merged since last monday", "should_trigger": true},
{"query": "what's new in hono this week? short version for the release post", "should_trigger": true},
{"query": "explain what commit 08807ce1 changes in the trie router", "should_trigger": false},
{"query": "the jsx tests are failing on cloneElement, can you fix them", "should_trigger": false},
{"query": "write a commit message for my staged changes", "should_trigger": false},
{"query": "who wrote most of the serve-static code?", "should_trigger": false},
{"query": "add a CHANGELOG entry format section to CONTRIBUTING.md", "should_trigger": false},
{"query": "show me the diff between main and my branch", "should_trigger": false},
{"query": "bump the version in package.json to the next patch", "should_trigger": false},
{"query": "why does the css helper minify url() like that?", "should_trigger": false},
{"query": "squash my last three commits into one", "should_trigger": false},
{"query": "review this PR for breaking changes before I merge it", "should_trigger": false}
]
Skrypt, który uruchamia każdą prośbę w osobnej świeżej sesji claude -p i liczy, czy wywołano narzędzie Skill.
trigger_test.py
"""Twenty-prompt trigger test for one skill, the method skill-creator describes, run on the real skill folder.
For each query: a fresh `claude -p` in the repo; the run counts as loaded if the Skill tool is called with the skill's name
before the run ends (stopped early as soon as it is). Usage:
python3 trigger_test.py <repo> <skill-name> <SKILL.md to install> <evals.json> <out.json> [workers]"""
import json, os, shutil, subprocess, sys
from concurrent.futures import ThreadPoolExecutor
repo, name, skill_md, evals_path, out = sys.argv[1:6]
workers = int(sys.argv[6]) if len(sys.argv) > 6 else 4
dest = os.path.join(repo, '.claude', 'skills', name)
os.makedirs(dest, exist_ok=True)
shutil.copy(skill_md, os.path.join(dest, 'SKILL.md'))
evals = json.load(open(evals_path))
allowed = ['Bash(rtk git log *)', 'Bash(git log *)', 'Bash(git show *)', 'Bash(date *)', 'Skill', 'Read']
def run(item):
cmd = ['claude', '-p', item['query'], '--output-format', 'stream-json', '--verbose', '--allowedTools', *allowed]
p = subprocess.Popen(cmd, cwd=repo, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
loaded = False
try:
for line in p.stdout:
try:
d = json.loads(line)
except ValueError:
continue
if d.get('type') == 'assistant':
for c in d['message'].get('content', []):
if c.get('type') == 'tool_use' and c['name'] == 'Skill' and name in json.dumps(c.get('input', {})):
loaded = True
if loaded or d.get('type') == 'result':
break
finally:
if p.poll() is None:
p.kill()
return {**item, 'loaded': loaded, 'pass': loaded == item['should_trigger']}
with ThreadPoolExecutor(workers) as ex:
results = list(ex.map(run, evals))
should = [r for r in results if r['should_trigger']]
shouldnt = [r for r in results if not r['should_trigger']]
summary = {'loaded_when_it_should': sum(r['loaded'] for r in should), 'of_should': len(should),
'loaded_when_it_should_not': sum(r['loaded'] for r in shouldnt), 'of_should_not': len(shouldnt),
'passed': sum(r['pass'] for r in results), 'total': len(results)}
json.dump({'skill': name, 'skill_md': skill_md, 'summary': summary, 'results': results}, open(out, 'w'), indent=2)
for r in results:
print(f"[{'PASS' if r['pass'] else 'FAIL'}] {'loaded ' if r['loaded'] else 'skipped'} expected={'load' if r['should_trigger'] else 'skip'}: {r['query']}")
print(json.dumps(summary))
Małe podsumowanie dla folderu uruchomień claude -p --output-format stream-json.
tally.py
"""Count whether a `claude -p --output-format stream-json` run loaded a given skill. Usage: python3 tally.py <skill> run1.jsonl [run2.jsonl ...]"""
import json, sys
skill, files = sys.argv[1], sys.argv[2:]
fired = 0
for f in files:
hit = False
for line in open(f):
try:
d = json.loads(line)
except ValueError:
continue
if d.get('type') == 'assistant':
for c in d['message'].get('content', []):
if c.get('type') == 'tool_use' and c['name'] == 'Skill' and skill in json.dumps(c.get('input', {})):
hit = True
fired += hit
print(f"{f}: {'loaded' if hit else 'skipped'}")
print(f"{skill}: loaded in {fired} of {len(files)} runs")
Zabezpieczenie dla skilla, który musi się odpalić: hook UserPromptSubmit, który wymienia skill.
hook.json
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo 'If the user asks about release notes, a changelog or what shipped this week, use the release-notes skill.'"
}
]
}
]
}
}
Zrób to w poniedziałek
- Zapisz prompt, który najczęściej wpisywałeś od nowa w zeszłym tygodniu, i dokładny wynik, jakiego od niego chcesz.
- Utwórz
.claude/skills/<name>/SKILL.mdz nazwą, opisem i krótką ponumerowaną treścią. - Przetestuj go po nazwie, potem otwórz świeżą sesję i poproś o zadanie zwykłymi słowami, bez nazwy.
- Jeśli jest pomijany, przeczytaj
/skills,claude --debugiclaude plugin validate .claude/skills, zanim cokolwiek zmienisz. - Przepisz opis według
description-template.md: co robi, kiedy go użyć, słowa, które wpisujesz. - Przetestuj ponownie w kilku świeżych sesjach, potem raz z ustawieniem skilla na
"off"wskillOverridesi raz z powrotem włączonego. - Napisz swoje dwadzieścia próśb, uruchom je przez
trigger_test.pyi powtarzaj, ilekroć zmieniasz opis. - Przy skillu, który nigdy nie może zawieść, zdecyduj: wywołaj go po nazwie albo dodaj hook.
Co zmierzyliśmy
To małe uruchomienia demo na roboczym klonie open-source'owego repozytorium Hono (s20), z Claude Code 2.1.294 i prośbą "what did we ship this week?" przy 8 commitach conventional niebędących mergami od poniedziałku 2026-10-12. To nie jest benchmark. Twój model, twoje skille i twoje repozytorium przesuną liczby.
| Test | Opis | Wynik |
|---|---|---|
| Zwykła prośba, 5 świeżych sesji | Podsumowanie: "Generates changelog documents from repository history." | Załadowany w 0 z 5 |
| Zwykła prośba, 5 świeżych sesji | Przepisany: co + kiedy + nasze słowa | Załadowany w 5 z 5 |
Wywołany po nazwie (/release-notes) |
Podsumowanie | Uruchomił się w 2 z 2 |
skillOverrides ustawione na off, potem on |
Przepisany | Off: pominięty. On: załadowany |
| Test dwudziestu próśb, przed przepisaniem | Podsumowanie | 4 z 10 próśb do załadowania załadowało, 0 z 10 bliskich pudeł, zaliczone 14 z 20 |
| Test dwudziestu próśb, po przepisaniu | Przepisany | 10 z 10 do załadowania, 0 z 10 bliskich pudeł, zaliczone 20 z 20 |
| Hook plus stary opis | Podsumowanie | Załadowany w 1 z 1 |
Przed przepisaniem 4 prośby, które się załadowały, to te zawierające "release notes", "changelog", "notes" lub "release": to efekt słów kluczowych jak na dłoni. Po przepisaniu szerszy opis nie przyciągnął ani jednego bliskiego pudła.
Ostrzeżenie, które znaleźliśmy, i uczciwa interpretacja. Z --debug nasza konfiguracja wypisała: "Skill listing over budget: 296 skills, 144833 chars > 30000 budget". Nasza konfiguracja ma zbyt wiele skilli, więc opisy są ucinane. Nasz projektowy skill mimo to się załadował ("Loaded 43 unique skills", jednym z nich był skill projektowy), a stary opis przetrwał cięcie: w teście dwudziestu próśb prośba zawierająca "changelog", słowo, które występuje tylko w opisie, załadowała skill. Nie udowodniliśmy, jak budżet traktuje nowy skill w ogólności. Jeśli masz dużo skilli, sprawdź to sam. /skills w naszej konfiguracji wypisało 277 skilli, a release-notes pokazywał się jako skill projektowy, włączony, bez odznaki user-only.
Zastrzeżenie dotyczące samego narzędzia: skrypt ewaluacji od Anthropic w ogóle nie widział skilla w naszej wersji, więc uruchomiliśmy te same prośby krótkim skryptem z zestawu. Metoda jest ta sama, jedna świeża sesja claude -p na prośbę.
Idź dalej
Napraw też odwrotny problem. Opis, który pasuje do zbyt wielu rzeczy, sprawia, że skill odpala tam, gdzie nie powinien. Dlatego połowę zestawu testowego stanowią bliskie pudła. Dodawaj słowa, których naprawdę używasz, a nie wszystkie, jakie przyjdą ci do głowy. s11
Pozwól optymalizatorowi zaproponować przepisanie. Pętla skill-creatora proponuje nowe opisy i zachowuje najlepszy według wyniku na zatrzymanym teście, by uniknąć przeuczenia pod zapytania, pod które stroiłeś. Użyj swoich dwudziestu próśb jako zestawu ewaluacyjnego. s4
Użyj sensownego promptu testowego. Skill-creator zauważa, że Claude sięga po skille tylko przy zadaniach, z którymi nie poradzi sobie łatwo sam, więc trywialna prośba w jednym kroku może nie wyzwolić skilla nawet przy idealnym dopasowaniu. s4
Idź dalej niż zwykły hook. Powyższy test zewnętrzny wykazał, że hook wymuszonej oceny, który każe Claude wypisać każdy skill jako tak lub nie, a potem aktywować, osiągnął 100% tam, gdzie pasywne przypomnienie nie, oraz że natywny hook type: "prompt" zachowywał się jak brak hooka. Nasz hook to prosta wersja i to jedno uruchomienie demo. s5 Zgłoszenia mówią, że podpowiedzi z hooków mogą słabnąć w długiej sesji. s10
Trzymaj treść krótko. W powyższym audycie jedyny zepsuty skill miał 1002 linie i MANDATORY na każdym kroku, a zdrowe były krótkie. s8
Co pominęliśmy. Nie omawialiśmy skilli z pluginów ani oceniających ewaluacji pluginów, które mają znaczenie tylko dla autorów pluginów, ani tego, jak porównywać skill z gołym modelem przez miesiące. Niektórzy zgłaszają też, że skille pisane dla słabszych modeli mogą stać się zbędnym balastem, więc testuj je co jakiś czas ponownie. Tego nie mierzyliśmy.
FAQ
Dlaczego /release-notes działa, a zwykłe słowa nie?
Wpisanie nazwy omija dopasowywanie. W zwykłych słowach Claude ma tylko nazwę i jedną linię opisu do dopasowania, więc podsumowanie niemające wspólnego słowa z twoją prośbą jest pomijane. s1
Czy muszę restartować Claude Code po edycji skilla?
Nie. Claude Code podejmuje dodany lub edytowany skill w ~/.claude/skills/ albo .claude/skills/ w bieżącej sesji. Jeśli tworzysz katalog skilli najwyższego poziomu, który nie istniał na starcie sesji, uruchom /reload-skills. s1
Jak długi może być opis?
Opis i when_to_use są razem ucinane przy 1536 znakach w liście, więc najważniejszy przypadek użycia daj na początku. Przewodnik platformy Anthropic osobno ogranicza opis do 1024 znaków dla skilli wgrywanych poza Claude Code. s1 s3
Czy pięć świeżych sesji wystarczy, by zaufać poprawce? Nie. Pokazuje kierunek. Nasz wynik to demo na jednym repozytorium, dlatego zestaw kończy się testem dwudziestu próśb, który możesz powtarzać po każdej zmianie opisu.
Kiedy użyć hooka zamiast skilla? Gdy reguła musi obowiązywać za każdym razem, a nie tylko wtedy, gdy Claude zauważy dopasowanie. Hook dodaje linię do każdej wiadomości, więc zachowaj go dla niewielu skilli, które muszą się odpalić. s9
AIDive