AIDive

Pack video

Il kit della skill che scatta: fai caricare da sola una skill di Claude Code

9 min di lettura

TL;DR e verdetto

Una skill di Claude Code può girare alla perfezione quando scrivi il suo nome e non caricarsi mai quando chiedi a parole tue. Prima di decidere se caricare una skill, Claude legge una riga per skill: il nome e la descrizione. La confronta con le tue parole. Se la riga non usa le tue parole, la skill resta in silenzio.

Nella nostra prova, una skill per le release notes con la descrizione "Generates changelog documents from repository history." si è caricata in 0 sessioni nuove su 5 con la richiesta "what did we ship this week?". Abbiamo riscritto quell'unica riga come cosa fa, quando usarla e le parole che scriviamo. La stessa richiesta l'ha poi caricata in 5 su 5.

Il verdetto:

  • Una skill vale per un compito che ripeti con un output fisso: note settimanali, la descrizione di una pull request, una checklist di release.
  • Non vale per una domanda occasionale, e non è lo strumento per una regola che deve valere sempre. Quello è il lavoro di un hook.
  • Un mucchio di skill che nessuno usa costa, perché oltre il budget dell'elenco Claude Code taglia le descrizioni, dalle meno usate.
  • La regola: scrivi la descrizione con le parole che scrivi davvero quando chiedi il compito.

Il kit qui sotto contiene il nostro file della skill prima e dopo, un modello di descrizione, la checklist per la sessione nuova e il test da venti richieste che abbiamo usato.

Cosa dicono la documentazione e il campo

Dove sta una skill. Una skill è una cartella con dentro un SKILL.md, in ~/.claude/skills/<skill-name>/ per ogni progetto oppure in .claude/skills/<skill-name>/ così la riceve anche il tuo team. Un file sciolto name.md non si carica. s1 s2

Claude legge la descrizione, non il corpo. L'elenco di nomi e descrizioni è nel contesto a ogni turno, e il corpo si carica solo quando la skill viene invocata. La documentazione dice che la descrizione è ciò che Claude usa per decidere quando applicare la skill. s1

L'elenco ha un tetto e un budget. La descrizione e il testo di when_to_use vengono troncati insieme a 1.536 caratteri. L'intero elenco è limitato all'1 % della finestra di contesto, e quando trabocca, Claude Code scarta le descrizioni partendo dalle skill che invochi meno. La documentazione dice che così si perdono le parole chiave di cui Claude ha bisogno per abbinare la tua richiesta. Vedi un avviso nel log di debug con --debug. s1

Un'intestazione malformata è la trappola che sembra un successo. Se lo YAML è malformato, il corpo si carica con metadati vuoti, quindi /skill-name funziona ancora ma Claude non riesce ad abbinare la tua descrizione. Esegui con --debug per vedere l'errore di parsing, oppure esegui claude plugin validate .claude/skills. s1 s2

Controlla il badge. Se una skill compare in /skills ma Claude non la invoca mai, o è impostato disable-model-invocation: true, oppure la descrizione non corrisponde a come formuli la richiesta. Un'etichetta user-only significa che Claude non la attiverà da solo. s2

Cosa contiene una buona descrizione. La guida di Anthropic dice che la descrizione deve includere cosa fa la skill e quando usarla, scritta alla terza persona, con i termini chiave che gli utenti scrivono. Osserva che Claude la usa per scegliere la skill giusta tra potenzialmente oltre 100. s3

Claude tende ad attivare troppo poco. Lo skill-creator di Anthropic dice "currently Claude has a tendency to 'undertrigger' skills" e consiglia una descrizione un po' "insistente". s18 Il suo test di attivazione usa 20 query, un misto di quelle che devono attivarla e di quelle che non devono, con i quasi-errori come le più preziose. Il ciclo le divide 60 % per l'addestramento e 40 % per un test separato, esegue ogni query 3 volte e tiene la descrizione con il miglior punteggio sul test. s4 Lo script di valutazione passa ogni query attraverso claude -p e controlla se la skill è stata usata. s19

L'attivazione si comporta come una corrispondenza per parole chiave. Sul binario reale, un test di terze parti (22 prompt, Sonnet 4.5) ha misurato una base di 55 % e 50 % in due esecuzioni, e un hook di valutazione forzata al 100 %. Un prompt con un token esplicito attivava quasi il 100 % delle volte, mentre la formulazione indiretta stava vicino allo 0 %. La lettura dell'autore: più vicino a una corrispondenza per parole chiave che semantica. È un solo blog, un modello più vecchio e un altro repository, quindi trattalo come prova a supporto, non come regola. s5

La descrizione è un innesco, non un riassunto. Un audit di nove skill reali è arrivato alla stessa conclusione: una descrizione che elenca situazioni concrete batte una che dice cos'è la skill. s8

Esiste anche il difetto opposto. Uno sviluppatore ha visto una skill scattare sui compiti sbagliati, ha scritto eval di attivazione, ha riscritto la descrizione come "Load when..." e ha notato che una skill che vale la pena tenere di solito nasce da un fallimento che sei stanco di vedere due volte. s11

Ci sono passati anche altri. La issue "Claude Code not aware of skills in .claude/skills" ha 75 reazioni ed era ancora aperta il 2026-10-17. Le soluzioni alternative nel thread includono un puntatore in CLAUDE.md e un hook. s7

Dove un hook batte una skill. La risposta più votata in un thread da 778 voti dice che una skill è "basically just a prompt you save for later" che Claude decide di usare o che chiami con un comando a barra, e che un hook serve quando le skill sono troppo lasche e vuoi determinismo. s9 Un altro thread avverte che i promemoria possono perdere forza in una sessione lunga. s10

Il kit

La skill prima e dopo. Cambia solo la riga della descrizione, il corpo è identico.

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

Il modello di descrizione, con la nostra descrizione reale come esempio svolto.

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 per la sessione nuova.

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

Il set di test da venti richieste: dieci richieste che devono caricare la skill, dieci quasi-errori che non devono. Modifica le query per adattarle al tuo repository.

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}
]

evals.json on GitHub

Lo script che esegue ogni richiesta nella sua sessione nuova di claude -p e conta se lo strumento Skill è stato chiamato.

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))

trigger_test.py on GitHub

Un piccolo conteggio per una cartella di esecuzioni di 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")

tally.py on GitHub

Il piano di riserva per quando non deve mancare: un hook UserPromptSubmit che nomina la 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.'"
          }
        ]
      }
    ]
  }
}

hook.json on GitHub

Da fare lunedì

  • Annota il prompt che hai riscritto di più la settimana scorsa e l'output esatto che ne vuoi.
  • Crea .claude/skills/<name>/SKILL.md con un nome, una descrizione e un corpo breve in passi numerati.
  • Provala per nome, poi apri una sessione nuova e chiedi il compito a parole tue senza il nome.
  • Se viene saltata, leggi /skills, claude --debug e claude plugin validate .claude/skills prima di modificare qualsiasi cosa.
  • Riscrivi la descrizione con description-template.md: cosa fa, quando usarla, le parole che scrivi.
  • Riprova in più sessioni nuove, poi una volta con la skill su "off" in skillOverrides e una volta di nuovo accesa.
  • Scrivi le tue venti richieste, eseguile con trigger_test.py e rieseguile ogni volta che cambi la descrizione.
  • Per una skill che non deve mai mancare, decidi: chiamarla per nome o aggiungere l'hook.

Cosa abbiamo misurato

Sono piccole prove dimostrative su un clone di lavoro del repository open source Hono (s20), con Claude Code 2.1.294 e la richiesta "what did we ship this week?" su 8 commit convenzionali non di merge da lunedì 2026-10-12. Non sono un benchmark. Il tuo modello, le tue skill e il tuo repository sposteranno i numeri.

Test Descrizione Risultato
Richiesta a parole nostre, 5 sessioni nuove Riassunto: "Generates changelog documents from repository history." Caricata in 0 su 5
Richiesta a parole nostre, 5 sessioni nuove Riscritta: cosa + quando + parole nostre Caricata in 5 su 5
Chiamata per nome (/release-notes) Riassunto Eseguita in 2 su 2
skillOverrides su off, poi on Riscritta Off: saltata. On: caricata
Test da venti richieste, prima della riscrittura Riassunto 4 su 10 richieste da caricare l'hanno caricata, 0 su 10 quasi-errori, 14 su 20 superate
Test da venti richieste, dopo la riscrittura Riscritta 10 su 10 da caricare, 0 su 10 quasi-errori, 20 su 20 superate
Hook più la vecchia descrizione Riassunto Caricata in 1 su 1

Prima della riscrittura, le 4 richieste che l'hanno caricata erano quelle con "release notes", "changelog", "notes" o "release": l'effetto parola chiave in piena vista. Dopo la riscrittura, la descrizione più ampia non ha attirato nemmeno un quasi-errore.

L'avviso che abbiamo trovato, e la lettura onesta. Con --debug il nostro setup ha stampato: "Skill listing over budget: 296 skills, 144833 chars > 30000 budget". Il nostro setup ha troppe skill, quindi le descrizioni vengono troncate. La skill del nostro progetto si è comunque caricata ("Loaded 43 unique skills", una delle quali la skill del progetto), e la vecchia descrizione è sopravvissuta al taglio: nel test da venti richieste, la richiesta con "changelog", una parola che vive solo nella descrizione, ha caricato la skill. Non abbiamo dimostrato come il budget tratti una skill nuova in generale. Se hai molte skill, fai il controllo tu. /skills sul nostro setup elencava 277 skill, e release-notes risultava come skill di progetto, attiva, senza etichetta user-only.

Una precisazione sullo strumento stesso: lo script di valutazione di Anthropic non vedeva affatto la skill sulla nostra versione, quindi abbiamo eseguito le stesse richieste con il breve script del kit. Il metodo è lo stesso, una sessione nuova di claude -p per richiesta.

Per andare oltre

Risolvi anche il problema opposto. Una descrizione che corrisponde a troppe cose fa scattare la skill dove non dovrebbe. Per questo metà del set di test sono quasi-errori. Aggiungi le parole che usi davvero, non tutte quelle che ti vengono in mente. s11

Lascia che sia l'ottimizzatore a proporre la riscrittura. Il ciclo dello skill-creator propone nuove descrizioni e tiene la migliore in base al punteggio sul test separato, per evitare l'overfitting alle query su cui hai lavorato. Usa le tue venti richieste come set di valutazione. s4

Usa un prompt di test sostanzioso. Lo skill-creator osserva che Claude consulta le skill solo per compiti che non sa gestire facilmente da solo, quindi una richiesta banale in un solo passo può non attivare una skill nemmeno con una corrispondenza perfetta. s4

Vai oltre l'hook semplice. Il test di terze parti citato sopra ha trovato che un hook di valutazione forzata, che fa elencare a Claude ogni skill come sì o no e poi attivare, ha raggiunto il 100 % dove un promemoria passivo no, e che un hook nativo type: "prompt" si comportava come nessun hook. Il nostro hook è la versione semplice, ed è una sola prova dimostrativa. s5 Ci sono segnalazioni di promemoria via hook che perdono forza in una sessione lunga. s10

Tieni breve il corpo. Nell'audit citato sopra, l'unica skill rotta era di 1.002 righe con MANDATORY ovunque, mentre quelle sane erano brevi. s8

Cosa abbiamo lasciato fuori. Non abbiamo trattato le skill di plugin e i valutatori per plugin, che interessano solo a chi scrive plugin, né come confrontare una skill con il modello nudo nell'arco di mesi. Alcune persone riferiscono anche che le skill scritte per modelli più deboli possono diventare zavorra, quindi riprovale ogni tanto. Non l'abbiamo misurato.

FAQ

Perché /release-notes funziona ma le parole nostre no? Scrivere il nome aggira la corrispondenza. A parole nostre Claude ha solo il nome e una riga di descrizione da confrontare, quindi un riassunto che non condivide nessuna parola con la tua richiesta viene saltato. s1

Devo riavviare Claude Code dopo aver modificato una skill? No. Claude Code recepisce una skill aggiunta o modificata in ~/.claude/skills/ o .claude/skills/ nella sessione corrente. Se crei una cartella skills di primo livello che non esisteva all'avvio della sessione, esegui /reload-skills. s1

Quanto può essere lunga la descrizione? La descrizione e when_to_use vengono tagliati insieme a 1.536 caratteri nell'elenco, quindi metti per primo il caso d'uso principale. La guida della piattaforma di Anthropic limita separatamente la descrizione a 1.024 caratteri per le skill caricate fuori da Claude Code. s1 s3

Bastano cinque sessioni nuove per fidarsi di una correzione? No. Mostrano la direzione. Il nostro risultato è una dimostrazione su un repository, ed è per questo che il kit si chiude con un test da venti richieste che puoi rieseguire ogni volta che cambi la descrizione.

Quando usare un hook al posto di una skill? Quando una regola deve valere ogni volta, non solo quando Claude nota una corrispondenza. Un hook aggiunge una riga a ogni messaggio, quindi riservalo alle poche skill che devono scattare. s9

File