AIDive

Pack de vídeo

El kit de la skill que salta: haz que una skill de Claude Code se cargue sola

9 min de lectura

TL;DR y veredicto

Una skill de Claude Code puede funcionar perfectamente cuando escribes su nombre y no cargarse nunca cuando pides con palabras normales. Antes de decidir cargar una skill, Claude lee una línea por skill: el nombre y la descripción. La compara con tus palabras. Si la línea no usa tus palabras, la skill se queda callada.

En nuestra ejecución, una skill de notas de versión con la descripción "Generates changelog documents from repository history." se cargó en 0 de 5 sesiones nuevas con la petición "what did we ship this week?". Reescribimos esa línea como qué hace, cuándo usarla y las palabras que escribimos. La misma petición la cargó entonces en 5 de 5.

El veredicto:

  • Una skill rinde en una tarea que repites con un resultado fijo: notas semanales, la descripción de una pull request, una checklist de versión.
  • No rinde para una pregunta puntual, y no es la herramienta para una regla que debe cumplirse siempre. Eso es trabajo de un hook.
  • Una pila de skills que nadie usa cuesta, porque al pasar el presupuesto del listado Claude Code recorta descripciones, las menos usadas primero.
  • La regla: escribe la descripción con las palabras que escribes de verdad cuando pides la tarea.

El kit de abajo trae nuestro archivo de skill antes y después, una plantilla de descripción, la checklist de sesión nueva y la prueba de veinte peticiones que usamos.

Qué dicen la documentación y el terreno

Dónde vive una skill. Una skill es una carpeta con un SKILL.md dentro, ya sea en ~/.claude/skills/<skill-name>/ para todos los proyectos o en .claude/skills/<skill-name>/ para que la tenga tu equipo. Un archivo name.md suelto no se carga. s1 s2

Claude lee la descripción, no el cuerpo. El listado de nombres y descripciones está en el contexto en cada turno, y el cuerpo solo se carga cuando se invoca la skill. La documentación dice que la descripción es lo que Claude usa para decidir cuándo aplicar la skill. s1

El listado tiene un tope y un presupuesto. El texto de la descripción y de when_to_use se trunca junto en 1.536 caracteres. Todo el listado está limitado al 1 % de la ventana de contexto, y cuando se desborda, Claude Code quita descripciones empezando por las skills que invocas menos. La documentación dice que esto elimina las palabras clave que Claude necesita para encajar tu petición. Ves un aviso en el registro de depuración con --debug. s1

Una cabecera mal formada es la trampa que parece un éxito. Si el YAML está mal formado, el cuerpo se carga con metadatos vacíos, así que /skill-name sigue funcionando pero Claude no puede encajar tu descripción. Ejecuta con --debug para ver el error de análisis, o ejecuta claude plugin validate .claude/skills. s1 s2

Mira la insignia. Si una skill aparece en /skills pero Claude nunca la invoca, o bien está definido disable-model-invocation: true, o bien la descripción no encaja con cómo formulas la petición. Una etiqueta user-only significa que Claude no la activará por su cuenta. s2

Qué contiene una buena descripción. La guía de Anthropic dice que la descripción debe incluir qué hace la skill y cuándo usarla, escrita en tercera persona, con los términos clave que escriben los usuarios. Señala que Claude la usa para elegir la skill correcta entre potencialmente más de 100. s3

Claude tiende a activar de menos. El skill-creator de Anthropic dice "currently Claude has a tendency to 'undertrigger' skills" y recomienda una descripción algo "pushy". s18 Su prueba de activación usa 20 consultas, una mezcla de las que deben activar y las que no, siendo los casi aciertos los más valiosos. El bucle las reparte en 60 % de entrenamiento y 40 % de prueba reservada, ejecuta cada consulta 3 veces y se queda con la descripción de mejor puntuación en la prueba. s4 El script de evaluación pasa cada consulta por claude -p y comprueba si se usó la skill. s19

La activación se comporta como una coincidencia por palabras clave. Sobre el binario real, una prueba de terceros (22 prompts, Sonnet 4.5) midió una base de 55 % y 50 % en dos ejecuciones, y un hook de evaluación forzada al 100 %. Un prompt con un término explícito activaba la skill cerca del 100 % de las veces, mientras que una formulación indirecta se quedaba cerca del 0 %. La lectura del autor: más cerca de una coincidencia por palabras clave que de una semántica. Es un solo blog, un modelo más antiguo y otro repositorio, así que tómalo como evidencia de apoyo, no como una regla. s5

La descripción es un disparador, no un resumen. Una auditoría de nueve skills reales llegó a la misma conclusión: una descripción que enumera situaciones concretas gana a una que dice qué es la skill. s8

El fallo contrario también existe. Un desarrollador vio cómo una skill se activaba en tareas equivocadas, escribió evaluaciones de activación, reescribió la descripción como "Load when...", y apuntó que una skill que vale la pena conservar suele nacer de un fallo que estás harto de ver dos veces. s11

Otros se toparon con esto. La incidencia "Claude Code not aware of skills in .claude/skills" tiene 75 reacciones y seguía abierta el 2026-10-17. Las soluciones alternativas del hilo incluyen un puntero en CLAUDE.md y un hook. s7

Dónde un hook gana a una skill. Una respuesta destacada en un hilo de 778 votos dice que una skill es "basically just a prompt you save for later" que Claude decide usar o que llamas con un comando slash, y que un hook sirve cuando las skills son demasiado laxas y quieres determinismo. s9 Otro hilo advierte de que los recordatorios pueden desvanecerse en una sesión larga. s10

El kit

La skill antes y después. Solo cambia la línea de descripción, el cuerpo es 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

La plantilla de descripción, con nuestra descripción real como ejemplo.

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 sesión nueva.

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

El conjunto de prueba de veinte peticiones: diez que deben cargar la skill y diez casi aciertos que no. Edita las consultas para adaptarlas a tu repositorio.

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

El script que ejecuta cada petición en su propia sesión nueva de claude -p y cuenta si se llamó a la herramienta 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))

trigger_test.py on GitHub

Un pequeño recuento para una carpeta de ejecuciones 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

El recurso para una skill que no puede fallar: un hook UserPromptSubmit que nombra 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

Haz esto el lunes

  • Anota el prompt que más reescribiste la semana pasada y el resultado exacto que quieres de él.
  • Crea .claude/skills/<name>/SKILL.md con un nombre, una descripción y un cuerpo corto numerado.
  • Pruébala por nombre, luego abre una sesión nueva y pide la tarea con palabras normales, sin el nombre.
  • Si se ignora, lee /skills, claude --debug y claude plugin validate .claude/skills antes de editar nada.
  • Reescribe la descripción con description-template.md: qué hace, cuándo usarla, las palabras que escribes.
  • Vuelve a probar en varias sesiones nuevas, luego una vez con la skill en "off" en skillOverrides y otra vez activada.
  • Escribe tus veinte peticiones, ejecútalas con trigger_test.py y vuelve a ejecutarlas cada vez que cambies la descripción.
  • Para una skill que no puede fallar nunca, decide: llamarla por su nombre o añadir el hook.

Lo que medimos

Son pequeñas ejecuciones de demostración sobre un clon de trabajo del repositorio open source Hono (s20), con Claude Code 2.1.294 y la petición "what did we ship this week?" sobre 8 commits convencionales sin merges desde el lunes 2026-10-12. No son un benchmark. Tu modelo, tus skills y tu repositorio moverán los números.

Prueba Descripción Resultado
Petición normal, 5 sesiones nuevas Resumen: "Generates changelog documents from repository history." Cargada en 0 de 5
Petición normal, 5 sesiones nuevas Reescrita: qué + cuándo + nuestras palabras Cargada en 5 de 5
Llamada por nombre (/release-notes) Resumen Ejecutada en 2 de 2
skillOverrides en off, luego on Reescrita Off: ignorada. On: cargada
Prueba de veinte peticiones, antes de la reescritura Resumen 4 de 10 peticiones a cargar la cargaron, 0 de 10 casi aciertos, 14 de 20 superadas
Prueba de veinte peticiones, después de la reescritura Reescrita 10 de 10 a cargar, 0 de 10 casi aciertos, 20 de 20 superadas
Hook más la descripción antigua Resumen Cargada en 1 de 1

Antes de la reescritura, las 4 peticiones que la cargaron eran las que contenían "release notes", "changelog", "notes" o "release": el efecto de las palabras clave a plena vista. Después de la reescritura, la descripción más amplia no atrajo ni un solo casi acierto.

El aviso que encontramos y la lectura honesta. Con --debug nuestra instalación mostró: "Skill listing over budget: 296 skills, 144833 chars > 30000 budget". Nuestra instalación lleva demasiadas skills, así que las descripciones se truncan. Nuestra skill de proyecto se cargó igualmente ("Loaded 43 unique skills", una de ellas la skill del proyecto), y la descripción antigua sobrevivió al recorte: en la prueba de veinte peticiones, la petición que contenía "changelog", una palabra que solo vive en la descripción, cargó la skill. No demostramos cómo trata el presupuesto a una skill nueva en general. Si llevas muchas skills, haz tú la comprobación. /skills en nuestra instalación listaba 277 skills, y release-notes aparecía como skill de proyecto, activada, sin etiqueta user-only.

Una salvedad sobre la propia herramienta: el script de evaluación de Anthropic no veía la skill en absoluto en nuestra versión, así que ejecutamos las mismas peticiones con el script corto del kit. El método es el mismo, una sesión nueva de claude -p por petición.

Para ir más lejos

Arregla también el problema contrario. Una descripción que encaja con demasiado hace que la skill salte donde no debe. Por eso la mitad del conjunto de prueba son casi aciertos. Añade las palabras que usas de verdad, no todas las que se te ocurran. s11

Deja que el optimizador proponga la reescritura. El bucle del skill-creator propone descripciones nuevas y se queda con la mejor según la puntuación de una prueba reservada, para evitar el sobreajuste a las consultas con las que afinaste. Usa tus veinte peticiones como conjunto de evaluación. s4

Usa un prompt de prueba sustancioso. El skill-creator señala que Claude solo consulta skills para tareas que no puede resolver fácilmente por sí solo, así que una petición trivial de un solo paso puede no activar una skill ni con una coincidencia perfecta. s4

Ve más allá del hook simple. La prueba de terceros de arriba encontró que un hook de evaluación forzada, que hace que Claude liste cada skill como sí o no y luego la active, llegaba al 100 % donde un recordatorio pasivo no, y que un hook nativo type: "prompt" se comportaba como no tener hook. Nuestro hook es la versión simple, y es una sola ejecución de demostración. s5 Hay quien cuenta que los recordatorios por hook pueden desvanecerse en una sesión larga. s10

Mantén el cuerpo corto. En la auditoría de arriba, la única skill rota tenía 1.002 líneas con MANDATORY por todas partes, mientras que las sanas eran cortas. s8

Lo que dejamos fuera. No cubrimos las skills de plugins ni los evaluadores de plugins, que solo importan a quien crea plugins, ni cómo comparar una skill con el modelo sin ella a lo largo de meses. Algunos cuentan también que las skills escritas para modelos más débiles pueden volverse peso muerto, así que vuelve a probarlas de vez en cuando. No lo medimos.

FAQ

¿Por qué /release-notes funciona y las palabras normales no? Escribir el nombre se salta la coincidencia. Con palabras normales, Claude solo tiene el nombre y una línea de descripción para comparar, así que un resumen que no comparte ninguna palabra con tu petición se ignora. s1

¿Tengo que reiniciar Claude Code después de editar una skill? No. Claude Code recoge una skill añadida o editada bajo ~/.claude/skills/ o .claude/skills/ en la sesión actual. Si creas un directorio de skills de nivel superior que no existía al empezar la sesión, ejecuta /reload-skills. s1

¿Qué longitud puede tener la descripción? La descripción y when_to_use se cortan juntas en 1.536 caracteres en el listado, así que pon primero el caso de uso clave. La guía de la plataforma de Anthropic limita por separado una descripción a 1.024 caracteres para las skills subidas fuera de Claude Code. s1 s3

¿Bastan cinco sesiones nuevas para fiarse de un arreglo? No. Muestran la dirección. Nuestro resultado es una demostración en un repositorio, por eso el kit termina con una prueba de veinte peticiones que puedes repetir cada vez que cambies la descripción.

¿Cuándo usar un hook en lugar de una skill? Cuando una regla debe cumplirse siempre, no solo cuando Claude nota una coincidencia. Un hook añade una línea a cada mensaje, así que resérvalo para las pocas skills que deben saltar. s9

Archivos