AIDive

Pack de vídeo

O kit da skill que dispara: faça uma skill do Claude Code carregar sozinha

9 min de leitura

TL;DR e veredicto

Uma skill do Claude Code pode rodar perfeitamente quando você digita o nome dela e mesmo assim nunca carregar quando você pede com palavras normais. Antes de decidir carregar uma skill, o Claude lê uma linha por skill: o nome e a descrição. Ele compara essa linha com as suas palavras. Se a linha não usa as suas palavras, a skill fica em silêncio.

Na nossa execução, uma skill de release notes com a descrição "Generates changelog documents from repository history." carregou em 0 de 5 sessões novas no pedido "what did we ship this week?". Reescrevemos essa única linha como o que ela faz, quando usar e as palavras que digitamos. O mesmo pedido passou a carregá-la em 5 de 5.

O veredicto:

  • Uma skill compensa para uma tarefa que você repete com uma saída fixa: notas semanais, a descrição de um pull request, um checklist de release.
  • Ela não compensa para uma pergunta pontual, e não é a ferramenta para uma regra que precisa valer sempre. Isso é trabalho de um hook.
  • Um monte de skills sem uso custa, porque acima do orçamento da lista o Claude Code corta descrições, começando pelas menos usadas.
  • A regra: escreva a descrição com as palavras que você realmente digita quando pede a tarefa.

O kit abaixo traz o nosso arquivo de skill antes e depois, um modelo de descrição, o checklist de sessão nova e o teste de vinte pedidos que usamos.

O que dizem a documentação e o campo

Onde mora uma skill. Uma skill é uma pasta com um SKILL.md dentro, em ~/.claude/skills/<skill-name>/ para todos os projetos ou em .claude/skills/<skill-name>/ para que o seu time também receba. Um arquivo solto name.md não carrega. s1 s2

O Claude lê a descrição, não o corpo. A lista de nomes e descrições fica no contexto a cada turno, e o corpo só carrega quando a skill é invocada. A documentação diz que a descrição é o que o Claude usa para decidir quando aplicar a skill. s1

A lista tem um teto e um orçamento. A descrição e o texto de when_to_use são truncados juntos em 1.536 caracteres. A lista inteira é limitada a 1 % da janela de contexto, e quando estoura, o Claude Code descarta descrições começando pelas skills que você menos invoca. A documentação diz que isso remove as palavras-chave de que o Claude precisa para combinar com o seu pedido. Você vê um aviso no log de debug com --debug. s1

Um cabeçalho malformado é a armadilha que parece sucesso. Se o YAML estiver malformado, o corpo carrega com metadados vazios, então /skill-name ainda funciona mas o Claude não consegue combinar a sua descrição. Rode com --debug para ver o erro de parsing, ou rode claude plugin validate .claude/skills. s1 s2

Confira o selo. Se uma skill aparece em /skills mas o Claude nunca a invoca, ou disable-model-invocation: true está definido, ou a descrição não combina com o jeito como você formula o pedido. Um selo user-only significa que o Claude não vai acioná-la sozinho. s2

O que uma boa descrição contém. O guia da Anthropic diz que a descrição deve incluir o que a skill faz e quando usar, escrita na terceira pessoa, com os termos-chave que os usuários digitam. Ele observa que o Claude a usa para escolher a skill certa entre potencialmente mais de 100. s3

O Claude tende a ativar de menos. O skill-creator da Anthropic diz "currently Claude has a tendency to 'undertrigger' skills" e recomenda uma descrição um pouco "insistente". s18 O teste de ativação dele usa 20 consultas, uma mistura de as que devem acionar e as que não devem, sendo os quase-acertos as mais valiosas. O ciclo divide em 60 % de treino e 40 % de teste separado, roda cada consulta 3 vezes e fica com a descrição de melhor nota no teste. s4 O script de avaliação passa cada consulta por claude -p e verifica se a skill foi usada. s19

A ativação se comporta como combinação de palavras-chave. No binário real, um teste de terceiros (22 prompts, Sonnet 4.5) mediu uma linha de base de 55 % e 50 % em duas execuções, e um hook de avaliação forçada em 100 %. Um prompt com um token explícito ativava quase 100 % das vezes, enquanto a formulação indireta ficava perto de 0 %. A leitura do autor: mais perto de combinação por palavra-chave do que de combinação semântica. É um único blog, um modelo mais antigo e outro repositório, então trate como evidência de apoio, não como regra. s5

A descrição é um gatilho, não um resumo. Uma auditoria de nove skills reais chegou à mesma conclusão: uma descrição que lista situações concretas vence uma que diz o que a skill é. s8

O defeito oposto também existe. Um desenvolvedor viu uma skill disparar nas tarefas erradas, escreveu evals de ativação, reescreveu a descrição como "Load when..." e observou que uma skill que vale a pena manter costuma nascer de uma falha que você cansou de ver duas vezes. s11

Outras pessoas passaram por isso. A issue "Claude Code not aware of skills in .claude/skills" tem 75 reações e ainda estava aberta em 2026-10-17. As soluções alternativas na discussão incluem um ponteiro no CLAUDE.md e um hook. s7

Onde um hook vence uma skill. A resposta mais votada numa discussão de 778 votos diz que uma skill é "basically just a prompt you save for later" que o Claude decide usar ou que você chama com um comando de barra, e que um hook serve para quando as skills são soltas demais e você quer determinismo. s9 Outra discussão avisa que lembretes podem perder força numa sessão longa. s10

O kit

A skill antes e depois. Só a linha da descrição muda, o corpo é idêntico.

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

O modelo de descrição, com a nossa descrição real como exemplo prático.

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

O checklist de sessão nova.

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

O conjunto de teste de vinte pedidos: dez pedidos que devem carregar a skill, dez quase-acertos que não devem. Edite as consultas para combinar com o seu repositório.

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

O script que roda cada pedido na sua própria sessão nova de claude -p e conta se a ferramenta Skill foi chamada.

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

Uma pequena contagem para uma pasta de execuções de 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

O plano B para quando não pode falhar: um hook UserPromptSubmit que cita a skill pelo nome.

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

Faça isto na segunda-feira

  • Anote o prompt que você mais redigitou na semana passada e a saída exata que quer dele.
  • Crie .claude/skills/<name>/SKILL.md com um nome, uma descrição e um corpo curto em passos numerados.
  • Teste pelo nome, depois abra uma sessão nova e peça a tarefa com palavras normais, sem o nome.
  • Se ela for ignorada, leia /skills, claude --debug e claude plugin validate .claude/skills antes de editar qualquer coisa.
  • Reescreva a descrição com description-template.md: o que ela faz, quando usar, as palavras que você digita.
  • Teste de novo em várias sessões novas, depois uma vez com a skill em "off" em skillOverrides e uma vez de volta ligada.
  • Escreva os seus vinte pedidos, rode com trigger_test.py e rode de novo sempre que mudar a descrição.
  • Para uma skill que não pode falhar nunca, decida: chamar pelo nome ou adicionar o hook.

O que medimos

São execuções pequenas de demonstração num clone de trabalho do repositório open source Hono (s20), com o Claude Code 2.1.294 e o pedido "what did we ship this week?" sobre 8 commits convencionais sem merge desde segunda 2026-10-12. Não são um benchmark. O seu modelo, as suas skills e o seu repositório vão mexer nos números.

Teste Descrição Resultado
Pedido normal, 5 sessões novas Resumo: "Generates changelog documents from repository history." Carregou em 0 de 5
Pedido normal, 5 sessões novas Reescrita: o quê + quando + nossas palavras Carregou em 5 de 5
Chamada pelo nome (/release-notes) Resumo Rodou em 2 de 2
skillOverrides em off, depois on Reescrita Off: ignorada. On: carregou
Teste de vinte pedidos, antes da reescrita Resumo 4 de 10 pedidos que deviam carregar carregaram, 0 de 10 quase-acertos, 14 de 20 passaram
Teste de vinte pedidos, depois da reescrita Reescrita 10 de 10 que deviam carregar, 0 de 10 quase-acertos, 20 de 20 passaram
Hook mais a descrição antiga Resumo Carregou em 1 de 1

Antes da reescrita, os 4 pedidos que carregaram eram os que continham "release notes", "changelog", "notes" ou "release": o efeito de palavra-chave à vista de todos. Depois da reescrita, a descrição mais ampla não puxou nenhum quase-acerto.

O aviso que encontramos, e a leitura honesta. Com --debug, o nosso setup imprimiu: "Skill listing over budget: 296 skills, 144833 chars > 30000 budget". O nosso setup tem skills demais, então as descrições são truncadas. A nossa skill do projeto ainda carregou ("Loaded 43 unique skills", uma delas a skill do projeto), e a descrição antiga sobreviveu ao corte: no teste de vinte pedidos, o pedido contendo "changelog", uma palavra que só existe na descrição, carregou a skill. Não provamos como o orçamento trata uma skill nova em geral. Se você carrega muitas skills, faça a verificação você mesmo. O /skills no nosso setup listou 277 skills, e release-notes aparecia como skill de projeto, ligada, sem selo user-only.

Uma ressalva sobre a própria ferramenta: o script de avaliação da Anthropic não enxergou a skill na nossa versão, então rodamos os mesmos pedidos com o script curto do kit. O método é o mesmo, uma sessão nova de claude -p por pedido.

Para ir além

Resolva o problema oposto também. Uma descrição que combina com coisas demais faz a skill disparar onde não deveria. Por isso metade do conjunto de teste são quase-acertos. Adicione as palavras que você realmente usa, não todas as que consegue imaginar. s11

Deixe o otimizador propor a reescrita. O ciclo do skill-creator propõe novas descrições e fica com a melhor pela nota no teste separado, para evitar sobreajuste às consultas em que você ajustou. Use os seus próprios vinte pedidos como conjunto de avaliação. s4

Use um prompt de teste substancial. O skill-creator observa que o Claude só consulta skills para tarefas que não consegue resolver facilmente sozinho, então um pedido trivial de um passo pode não acionar uma skill mesmo com combinação perfeita. s4

Vá além do hook simples. O teste de terceiros acima descobriu que um hook de avaliação forçada, que faz o Claude listar cada skill como sim ou não e só então ativar, chegou a 100 % onde um lembrete passivo não chegou, e que um hook nativo type: "prompt" se comportou como nenhum hook. O nosso hook é a versão simples, e é uma execução de demonstração. s5 Há relatos de que lembretes por hook perdem força numa sessão longa. s10

Mantenha o corpo curto. Na auditoria acima, a única skill quebrada tinha 1.002 linhas com MANDATORY por toda parte, enquanto as saudáveis eram curtas. s8

O que deixamos de fora. Não cobrimos skills de plugins e os avaliadores de plugins, que só interessam a quem cria plugins, nem como comparar uma skill com o modelo puro ao longo de meses. Algumas pessoas também relatam que skills escritas para modelos mais fracos podem virar peso morto, então teste de novo de vez em quando. Não medimos isso.

FAQ

Por que /release-notes funciona mas as palavras normais não? Digitar o nome pula a combinação. Com palavras normais o Claude só tem o nome e uma linha de descrição para comparar, então um resumo que não compartilha nenhuma palavra com o seu pedido é ignorado. s1

Preciso reiniciar o Claude Code depois de editar uma skill? Não. O Claude Code pega uma skill adicionada ou editada em ~/.claude/skills/ ou .claude/skills/ na sessão atual. Se você criar uma pasta de skills de nível superior que não existia quando a sessão começou, rode /reload-skills. s1

Qual o tamanho máximo da descrição? A descrição e o when_to_use são cortados juntos em 1.536 caracteres na lista, então coloque o caso de uso principal primeiro. O guia da plataforma da Anthropic limita separadamente a descrição a 1.024 caracteres para skills enviadas fora do Claude Code. s1 s3

Cinco sessões novas bastam para confiar numa correção? Não. Elas mostram a direção. O nosso resultado é uma demonstração em um repositório, e por isso o kit termina com um teste de vinte pedidos que você pode rodar de novo sempre que mudar a descrição.

Quando usar um hook em vez de uma skill? Quando uma regra precisa valer sempre, não só quando o Claude nota uma combinação. Um hook adiciona uma linha a cada mensagem, então reserve para as poucas skills que precisam disparar. s9

Arquivos