TL;DR
claude plugin validate --strict ./mi-pluginantes de compartirlo. Con--strict, un aviso (un campo mal escrito, una skill sindescription) devuelve exit 1, que es justo lo que quieres en el CI. Apúntalo a la ruta real de la carpeta: no sigue symlinks.
Un plugin roto no protesta. Claude Code carga lo que entiende, ignora en silencio lo que no, y el usuario se queda mirando una sesión donde tu skill no aparece por ningún lado. No hay traza, no hay aviso, no hay nada que buscar en un log.
claude plugin validate es el paso previo: lee el manifiesto, las skills, los agentes y los hooks de una tirada, y te dice qué líneas se van a caer al cargar. Es lo mismo que hace /doctor con tu instalación, pero apuntado a lo que vas a repartir en vez de a lo que tienes montado.
Lo que ves cuando algo está mal
$ claude plugin validate ./mi-plugin
Validating plugin manifest: ./mi-plugin/.claude-plugin/plugin.json
⚠ Found 2 warnings:
❯ autor: Unknown field 'autor' — did you mean 'author'? Claude Code ignores
unrecognized fields at load time, so this field has no effect.
❯ author: No author information provided. Consider adding author details
Validating skill: ./mi-plugin/skills/hello/SKILL.md
⚠ Found 1 warning:
❯ description: No description in frontmatter. A description helps users and
Claude understand when to use this skill.
✔ Validation passed with warnings
Solo imprime los archivos con algo que decir. Si tus veinte skills están bien, no las lista: verás el encabezado y el check verde, y eso significa que las ha leído todas, no que se las haya saltado.
La distinción que importa es entre aviso y error. Un aviso es algo que carga igual: el campo autor existirá en tu JSON y Claude Code seguirá sin leerlo nunca. Un error es algo que no carga: JSON inválido, un name que falta, una ruta de commands que apunta a una carpeta inexistente, y ahí el comando devuelve exit 1.
Los avisos son el 90% del valor, y por defecto no fallan
Un campo con una letra de más es exactamente el fallo que se cuela en una revisión humana y que nadie nota hasta que un usuario pregunta por qué tu comando no existe. Por eso está --strict:
1. En tu máquina, antes de compartir
claude plugin validate --strict ./mi-plugin
Cualquier aviso pasa a ser error y la salida termina en ✘ Validation failed (--strict treats warnings as errors) con exit 1.
2. En el CI, antes del merge
- run: npx @anthropic-ai/claude-code plugin validate --strict .
El exit code es el contrato. Si prefieres procesar el informe en vez de leerlo, --json devuelve la misma estructura (errors, warnings, notes por archivo) con los mismos códigos de salida.
Dónde apuntarlo, que no es obvio
Con manifiesto, le pasas la carpeta del plugin y busca .claude-plugin/plugin.json o .claude-plugin/marketplace.json. Sin manifiesto, valida un directorio suelto de componentes, pero solo si se llama como el componente: skills/, agents/ o commands/. Apuntarlo a mi-plugin/ a secas, o a la carpeta de una skill concreta, responde No manifest found in directory.
Y hay un detalle que a mí me dejó un resultado vacío la primera vez. Mis skills viven en el repo y ~/.claude/skills son symlinks:
$ claude plugin validate ~/.claude/skills
⚠ Found 1 warning:
❯ directory: 15 entries here are symlinks and were not read — components are
read without following symlinks. A session loading this directory does
follow them, so validate the real paths separately.
Quince skills, cero validadas. La sesión sí las carga, así que el comando avisa y te manda a la ruta real. Apuntado a ~/code/ai-infra/skills pasa limpio.
Lo que un check verde no promete
Valida lo que el cargador cargaría, y solo eso. Tres huecos que conviene tener en la cabeza:
Si pones contenido en la carpeta equivocada, no existe para nadie. Una skill dentro de .claude-plugin/skills/ no se escanea, así que el comando termina en ✔ Validation passed sin mencionarla. No es un fallo suyo: si el runtime no la va a leer, no hay nada que validar. Pero un check verde no te dice que tu skill esté colocada donde crees.
En un marketplace.json, un source que apunta a una carpeta inexistente pasa, incluso con --strict. En plugin.json esa misma ruta rota sí es error, así que la comprobación existe; simplemente no llega al catálogo.
Y no valida el contenido. Que una skill tenga description no significa que esté escrita para que Claude sepa cuándo invocarla, ni que tus hooks hagan lo que crees. Eso se prueba usándolo, o con claude plugin eval. validate solo garantiza que llega entero al otro lado.
Referencia
| Qué le pasas | Qué hace |
|---|---|
./mi-plugin (con .claude-plugin/) |
Valida el manifiesto y todo su contenido |
./skills, ./agents, ./commands |
Valida los componentes sueltos, sin manifiesto |
--strict |
Convierte los avisos en errores (exit 1) |
--json |
El mismo informe en JSON, mismos exit codes |
| Exit 0 / exit 1 | Pasa (con o sin avisos) / falla |
Pásalo antes de cada release y el --strict te ahorra el issue. Si todavía no tienes un plugin, empieza por lo que es; si ya lo tienes y lo que falta es repartirlo, el marketplace es el siguiente paso.
Documentación oficial: Plugins reference
Requisitos
- Claude Code v2.1.233 o superior para validar directorios sin manifiesto. Todo lo de arriba está ejecutado en la v2.1.263.