TL;DR et verdict
Un skill Claude Code peut marcher parfaitement quand vous tapez son nom et ne jamais se charger quand vous demandez en mots simples. Avant de décider de charger un skill, Claude lit une ligne par skill : le nom et la description. Il compare cette ligne à vos mots. Si la ligne n'emploie pas vos mots, le skill reste muet.
Dans notre essai, un skill de release notes avec la description "Generates changelog documents from repository history." s'est chargé dans 0 session neuve sur 5 pour la demande "what did we ship this week?". On a réécrit cette seule ligne : ce que fait le skill, quand l'utiliser et les mots qu'on tape. La même demande l'a alors chargé 5 fois sur 5.
Le verdict :
- Un skill sert pour une tâche que vous répétez avec un résultat fixe : des notes hebdomadaires, une description de pull request, une checklist de version.
- Il ne sert pas pour une question ponctuelle, et ce n'est pas l'outil pour une règle qui doit toujours tenir. C'est le travail d'un hook.
- Une pile de skills que personne n'utilise coûte, parce qu'au-delà du budget de la liste Claude Code coupe des descriptions, les moins utilisées d'abord.
- La règle d'or : écrivez la description avec les mots que vous tapez vraiment quand vous demandez la tâche.
Le kit ci-dessous contient notre fichier de skill avant et après, un modèle de description, la checklist de session neuve et le test de vingt demandes qu'on a utilisé.
Ce que disent la doc et le terrain
Où vit un skill. Un skill est un dossier avec un SKILL.md dedans, soit dans ~/.claude/skills/<skill-name>/ pour tous les projets, soit dans .claude/skills/<skill-name>/ pour que votre équipe l'ait aussi. Un fichier name.md posé à plat ne se charge pas. s1 s2
Claude lit la description, pas le corps. La liste des noms et des descriptions est dans le contexte à chaque tour, et le corps ne se charge que lorsque le skill est invoqué. La doc dit que la description est ce que Claude utilise pour décider quand appliquer le skill. s1
La liste a un plafond et un budget. Le texte de la description et de when_to_use est tronqué ensemble à 1 536 caractères. Toute la liste est limitée à 1 % de la fenêtre de contexte, et quand elle déborde, Claude Code retire des descriptions en commençant par les skills que vous invoquez le moins. La doc précise que cela supprime les mots-clés dont Claude a besoin pour faire correspondre votre demande. Un avertissement apparaît dans le journal de debug avec --debug. s1
Un en-tête mal formé est le piège qui ressemble à une réussite. Si le YAML est mal formé, le corps se charge avec des métadonnées vides : /skill-name marche toujours, mais Claude ne peut pas faire correspondre votre description. Lancez avec --debug pour voir l'erreur d'analyse, ou lancez claude plugin validate .claude/skills. s1 s2
Vérifiez le badge. Si un skill apparaît dans /skills mais que Claude ne l'invoque jamais, soit disable-model-invocation: true est défini, soit la description ne correspond pas à la façon dont vous formulez la demande. Un badge user-only signifie que Claude ne le déclenchera pas de lui-même. s2
Ce que contient une bonne description. Le guide d'Anthropic dit que la description doit inclure ce que fait le skill et quand l'utiliser, rédigée à la troisième personne, avec les termes clés que les utilisateurs tapent. Il note que Claude s'en sert pour choisir le bon skill parmi potentiellement plus de 100. s3
Claude a tendance à sous-déclencher. Le skill-creator d'Anthropic dit "currently Claude has a tendency to 'undertrigger' skills" et recommande une description un peu "pushy". s18 Son test de déclenchement utilise 20 requêtes, un mélange de cas qui doivent déclencher et de cas qui ne le doivent pas, les quasi-ratés étant les plus précieux. La boucle les répartit à 60 % pour l'entraînement et 40 % pour un test mis de côté, lance chaque requête 3 fois, et garde la description qui a le meilleur score au test. s4 Le script d'évaluation passe chaque requête par claude -p et vérifie si le skill a été utilisé. s19
L'activation se comporte comme une correspondance par mots-clés. Sur le vrai binaire, un test tiers (22 prompts, Sonnet 4.5) a mesuré une base de 55 % et 50 % sur deux exécutions, et un hook d'évaluation forcée à 100 %. Un prompt avec un terme explicite activait le skill près de 100 % du temps, tandis qu'une formulation indirecte restait près de 0 %. La lecture de l'auteur : plus proche d'une correspondance par mots-clés que d'une correspondance sémantique. C'est un seul blog, un modèle plus ancien et un autre dépôt, donc prenez-le comme un indice, pas comme une règle. s5
La description est un déclencheur, pas un résumé. Un audit de neuf vrais skills arrive à la même conclusion : une description qui liste des situations concrètes vaut mieux qu'une qui dit ce qu'est le skill. s8
L'échec inverse existe aussi. Un développeur a vu un skill se déclencher sur les mauvaises tâches, a écrit des évaluations de déclenchement, a réécrit la description en "Load when...", et a noté qu'un skill qui mérite d'être gardé naît souvent d'un échec qu'on est las de revoir deux fois. s11
D'autres l'ont vécu. Le ticket "Claude Code not aware of skills in .claude/skills" compte 75 réactions et était encore ouvert le 2026-10-17. Les contournements dans le fil incluent un pointeur dans CLAUDE.md et un hook. s7
Là où un hook bat un skill. Une réponse en tête d'un fil à 778 votes dit qu'un skill est "basically just a prompt you save for later" que Claude décide d'utiliser ou que vous appelez avec une commande slash, et qu'un hook sert quand les skills sont trop lâches et que vous voulez du déterminisme. s9 Un autre fil prévient que les rappels peuvent s'estomper au fil d'une longue session. s10
Le kit
Le skill avant et après. Seule la ligne de description change, le corps est identique.
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
Le modèle de description, avec notre vraie description comme exemple.
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
La checklist de session neuve.
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
Le jeu de test de vingt demandes : dix demandes qui doivent charger le skill, dix quasi-ratés qui ne le doivent pas. Modifiez les requêtes pour les adapter à votre dépôt.
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}
]
Le script qui lance chaque demande dans sa propre session neuve claude -p et compte si l'outil Skill a été appelé.
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))
Un petit récapitulatif pour un dossier d'exécutions 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")
Le recours pour un skill à ne jamais rater : un hook UserPromptSubmit qui nomme le 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.'"
}
]
}
]
}
}
À faire lundi
- Notez le prompt que vous avez le plus retapé la semaine dernière, et le résultat exact que vous en voulez.
- Créez
.claude/skills/<name>/SKILL.mdavec un nom, une description et un court corps numéroté. - Testez-le par son nom, puis ouvrez une session neuve et demandez la tâche en mots simples, sans le nom.
- S'il est ignoré, lisez
/skills,claude --debugetclaude plugin validate .claude/skillsavant de rien modifier. - Réécrivez la description avec
description-template.md: ce qu'il fait, quand l'utiliser, les mots que vous tapez. - Retestez dans plusieurs sessions neuves, puis une fois avec le skill à
"off"dansskillOverrideset une fois de nouveau activé. - Écrivez vos vingt demandes, lancez-les avec
trigger_test.py, et relancez-les à chaque changement de description. - Pour un skill qui ne doit jamais rater, décidez : l'appeler par son nom, ou ajouter le hook.
Ce qu'on a mesuré
Ce sont de petites exécutions de démonstration sur un clone de travail du dépôt open source Hono (s20), avec Claude Code 2.1.294 et la demande "what did we ship this week?" sur 8 commits conventionnels hors fusion depuis lundi 2026-10-12. Ce n'est pas un benchmark. Votre modèle, vos skills et votre dépôt feront bouger les chiffres.
| Test | Description | Résultat |
|---|---|---|
| Demande simple, 5 sessions neuves | Résumé : "Generates changelog documents from repository history." | Chargé dans 0 sur 5 |
| Demande simple, 5 sessions neuves | Réécrite : quoi + quand + nos mots | Chargé dans 5 sur 5 |
Appelé par son nom (/release-notes) |
Résumé | Exécuté dans 2 sur 2 |
skillOverrides à off, puis on |
Réécrite | Off : ignoré. On : chargé |
| Test de vingt demandes, avant la réécriture | Résumé | 4 sur 10 demandes à charger l'ont chargé, 0 sur 10 quasi-ratés, 14 sur 20 réussies |
| Test de vingt demandes, après la réécriture | Réécrite | 10 sur 10 à charger, 0 sur 10 quasi-ratés, 20 sur 20 réussies |
| Hook plus l'ancienne description | Résumé | Chargé dans 1 sur 1 |
Avant la réécriture, les 4 demandes qui ont chargé le skill étaient celles qui contenaient "release notes", "changelog", "notes" ou "release" : c'est l'effet mot-clé en pleine lumière. Après la réécriture, la description élargie n'a attiré aucun quasi-raté.
L'avertissement qu'on a trouvé, et la lecture honnête. Avec --debug, notre installation a affiché : "Skill listing over budget: 296 skills, 144833 chars > 30000 budget". Notre installation porte trop de skills, donc les descriptions sont tronquées. Notre skill de projet s'est quand même chargé ("Loaded 43 unique skills", dont le skill de projet), et l'ancienne description a survécu à la coupe : dans le test de vingt demandes, la requête contenant "changelog", un mot qui n'existe que dans la description, a chargé le skill. On n'a pas prouvé comment le budget traite un skill neuf en général. Si vous portez beaucoup de skills, faites la vérification vous-même. /skills sur notre installation listait 277 skills, et release-notes apparaissait comme skill de projet, activé, sans badge user-only.
Une réserve sur l'outil lui-même : le script d'évaluation d'Anthropic ne voyait pas du tout le skill sur notre version, alors on a lancé les mêmes demandes avec le court script du kit. La méthode est la même, une session neuve claude -p par demande.
Aller plus loin
Réglez aussi le problème inverse. Une description qui correspond à trop de choses fait se déclencher le skill là où il ne devrait pas. C'est pour ça que la moitié du jeu de test, ce sont des quasi-ratés. Ajoutez les mots que vous employez vraiment, pas tous ceux auxquels vous pensez. s11
Laissez l'optimiseur proposer la réécriture. La boucle du skill-creator propose de nouvelles descriptions et garde la meilleure selon un score de test mis de côté, pour éviter le surajustement aux requêtes sur lesquelles vous avez réglé. Utilisez vos vingt demandes comme jeu d'évaluation. s4
Utilisez un prompt de test substantiel. Le skill-creator note que Claude ne consulte les skills que pour les tâches qu'il ne sait pas faire facilement seul, donc une demande triviale en une étape peut ne pas déclencher de skill même avec une correspondance parfaite. s4
Allez plus loin que le hook simple. Le test tiers ci-dessus a trouvé qu'un hook d'évaluation forcée, qui fait lister à Claude chaque skill en oui ou non puis l'activer, atteignait 100 % là où un rappel passif n'y arrivait pas, et qu'un hook natif type: "prompt" se comportait comme l'absence de hook. Notre hook est la version simple, et c'est une seule exécution de démonstration. s5 Des témoignages disent que les rappels par hook peuvent s'estomper dans une longue session. s10
Gardez le corps court. Dans l'audit ci-dessus, le seul skill cassé faisait 1 002 lignes avec MANDATORY partout, tandis que les skills sains étaient courts. s8
Ce qu'on a laissé de côté. On n'a pas couvert les skills de plugins ni les évaluateurs d'évaluation de plugins, qui n'intéressent que les auteurs de plugins, ni la comparaison d'un skill avec le modèle nu sur plusieurs mois. Certains rapportent aussi que des skills écrits pour des modèles plus faibles peuvent devenir du poids mort, alors retestez-les de temps en temps. On ne l'a pas mesuré.
FAQ
Pourquoi /release-notes marche et pas les mots simples ?
Taper le nom contourne la correspondance. En mots simples, Claude n'a que le nom et une ligne de description à comparer, donc un résumé qui ne partage aucun mot avec votre demande est ignoré. s1
Dois-je redémarrer Claude Code après avoir modifié un skill ?
Non. Claude Code prend en compte un skill ajouté ou modifié sous ~/.claude/skills/ ou .claude/skills/ dans la session en cours. Si vous créez un dossier skills de premier niveau qui n'existait pas au démarrage de la session, lancez /reload-skills. s1
Quelle longueur peut avoir la description ?
La description et when_to_use sont coupées ensemble à 1 536 caractères dans la liste, donc mettez le cas d'usage principal en premier. Le guide de la plateforme d'Anthropic plafonne séparément une description à 1 024 caractères pour les skills téléversés en dehors de Claude Code. s1 s3
Cinq sessions neuves suffisent-elles pour faire confiance à une correction ? Non. Elles montrent la direction. Notre résultat est une démonstration sur un dépôt, c'est pourquoi le kit se termine par un test de vingt demandes que vous pouvez relancer à chaque changement de description.
Quand utiliser un hook plutôt qu'un skill ? Quand une règle doit tenir à chaque fois, pas seulement quand Claude remarque une correspondance. Un hook ajoute une ligne à chaque message, alors gardez-le pour les quelques skills qui doivent se déclencher. s9
AIDive