Skip to content

Tutorial de Claude Code Mods: cómo crear, probar e instalar tu primer mod, con 15 ejemplos de la comunidad

Actualizado el

Los mods de Claude Code (v2.1.287+) son plugins de funciones TypeScript que dibujan paneles, añaden comandos slash instantáneos y filtran las llamadas a herramientas. En este tutorial verás cómo crear y probar tres mods que funcionan, 15 mods de la comunidad y qué revisar antes de instalar uno.

Un mod de Claude Code es un plugin hecho de funciones JavaScript o TypeScript que se ejecutan dentro de Claude Code. Un mod puede dibujar un panel o una fila encima del prompt, añadir un comando slash que se ejecuta al instante y detener o reescribir una llamada a herramienta antes de que se ejecute. Los mods llegaron con Claude Code 2.1.287 el 1 de octubre de 2026 y vienen activados por defecto.

Elige la forma más rápida de empezar:

Si quieresHaz esto
Tener un mod sin escribir códigoDescríbelo en una sesión, por ejemplo "crea un mod que muestre cuánto tardó cada turno". Claude lo escribe y te pregunta una vez si quieres activar la recarga en caliente
Instalar el mod de otra persona/plugin install <name>@<marketplace> en una sesión de terminal, o claude plugin install <name>@<marketplace> en tu shell
Cargar una carpeta mientras desarrollasclaude --plugin-dir ./my-mod
Probar el agente lateral integrado/plugin enable cc-plugin-you-should-know@builtin

Primero comprueba tu versión. claude --version debería mostrar 2.1.287 o posterior. La app de escritorio trae su propia copia de Claude Code, y ahí los mods funcionan desde la 2.1.286. Escribe /status en la pestaña Code para ver cuál tienes.

Qué probé. El 7 de octubre de 2026 creé tres mods con Claude Code 2.1.293 en la pestaña Code de la app de escritorio, en macOS: un guardián de comandos, un medidor de turnos encima del prompt y un comando /pyenv para trabajar con Python. Los tres pasan claude plugin validate y 8 pruebas automatizadas con claude plugin test. También ejecuté el guardián en vivo, en una sesión con la recarga en caliente activada. Las secciones sobre la comunidad se basan en unos 50 artículos en japonés de Zenn, Qiita y note.com, escritos del 1 al 7 de octubre, en la documentación oficial de los mods (opens in a new tab) y en los datos de escaneo del catálogo awesome-claude-code-mods (opens in a new tab) del 6 de octubre.

Qué es un mod y cuándo usar otra cosa

Antes de los mods, ampliabas Claude Code desde fuera, con hooks de configuración, skills, un script para la línea de estado o un servidor MCP. La diferencia está en dónde se ejecuta el código. Un hook de configuración es un script que Claude Code lanza desde fuera. Las funciones de un mod se ejecutan dentro del propio proceso de Claude Code, así que un mod puede dibujar en la interfaz y ninguna de las otras opciones puede.

La página oficial de introducción los compara más o menos así:

ModHook de configuraciónSkillServidor MCP
Qué esFunciones JS o TS en un pluginUn comando de shell, una llamada HTTP o un prompt en settings.jsonUn archivo SKILL.md con instruccionesUn proceso externo que le da herramientas a Claude
Puede dibujar en la interfazSíNoNoNo
Qué puede cambiarLlamadas a herramientas, prompts, comandos, turnos, la interfazSi una llamada sigue adelante, sus argumentos y su resultado, contexto adicionalLo que Claude sabe y haceQué herramientas tiene Claude
Elígelo cuandoQuieres un panel, una franja, un comando instantáneo o reescribir un eventoYa tienes un script que bloquea o registraPegas una y otra vez las mismas instruccionesClaude necesita llegar a un sistema externo

Una trampa con los nombres. En las páginas de los mods, "hook" significa una función de un mod, y el tipo antiguo se llama "settings hook", lo que aquí llamo hook de configuración. Los hooks de configuración siguen funcionando y no están obsoletos. Un mismo plugin puede contener a la vez un mod, una skill y un servidor MCP.

Dónde funcionan los mods

Los hooks se ejecutan casi en todas partes. Los mods solo dibujan en la terminal y en la app de escritorio.

Dónde ejecutas Claude CodeSe ejecutan los hooksAparece la interfaz del mod
claude en una terminal, incluidas las terminales de editores y JetBrainsSíSí
App de escritorio, pestaña CodeSíSí, salvo los elementos exclusivos de la terminal
App de escritorio, sesión WSLNoNo
Panel de chat de la extensión de VS CodeSíNo
claude -p y el Agent SDKSíNo
Sesiones en la nubeSí, si el plugin llega a la sesiónNo

Esta tabla sale de la página oficial de introducción. En la práctica, un mod guardián te sigue protegiendo en VS Code y en claude -p, mientras que un medidor no tiene dónde dibujar.

Cuidado con tener dos copias de Claude Code. Varios autores japoneses recibieron errores confusos de validate porque una CLI de terminal antigua instalada con Homebrew, las versiones 2.1.226 y 2.1.234 según sus artículos, convivía con la 2.1.286 que trae la app de escritorio. Comprueba la versión en cada lugar donde uses Claude Code.

Cómo se organiza un mod

Un mod pequeño ocupa tres archivos:

safe-shell/
├── .claude-plugin/
│   └── plugin.json      name, version, description
└── hooks/
    ├── hooks.json       { "modules": ["./register.ts"] }
    └── register.ts      your code

register.ts exporta una sola función, register(on). Cada llamada a on(event, matcher, hook) añade un hook, y todos los hooks reciben los mismos tres argumentos.

  • $ es el motor. Todo lo que queda fuera de tu propio código pasa por él: $.ui.toast, $.process.run, $.state, $.model.complete.
  • e es el evento, por ejemplo el comando de Bash que Claude está a punto de ejecutar.
  • next(e) pasa el evento a los demás mods y después a Claude Code. Si devuelves un valor sin llamarlo, tu hook responde por su cuenta. Si llamas a next({ ...e, command }), cambias lo que ocurre.

El módulo no tiene Node ni DOM. Ese límite es lo que permite a Claude Code listar todo lo que llama un mod antes de que se ejecute. Pruébalo con la carpeta de cualquier mod:

claude plugin validate ./safe-shell

Mod 1: un guardián que bloquea comandos destructivos

Si ejecutas Claude en auto mode o con bypass permissions, nada lo frena antes de un git reset --hard. Este mod rechaza tres tipos de comando de shell y deja los archivos .env fuera de la conversación.

import type { Register } from 'claude-code'
 
// Shell commands an agent should never run without a human typing them.
const BLOCKED: { pattern: RegExp; reason: string }[] = [
  {
    pattern: /\brm\s+-[a-zA-Z]*[rR][a-zA-Z]*\s+(\/|~|\$HOME)\/?\*?(\s|$)/,
    reason: 'recursive delete of / or the home folder',
  },
  {
    pattern: /\bgit\s+push\b.*(--force\b|--force-with-lease\b|\s-f\b).*\b(main|master)\b/,
    reason: 'force push to main or master',
  },
  {
    pattern: /\bgit\s+reset\s+--hard\b/,
    reason: 'git reset --hard throws away uncommitted work',
  },
]
 
// .env, .env.local, config/.env.production ... but not .env.example.
const isSecretEnvFile = (path: string) =>
  /(^|\/)\.env(\.[^/]+)?$/.test(path) && !/\.(example|sample|template)$/.test(path)
 
export const register: Register = on => {
  on('tool.call', { tool: 'Bash' }, ($, e, next) => {
    const hit = BLOCKED.find(rule => rule.pattern.test(e.command))
    if (hit === undefined) {
      return next(e)
    }
    $.ui.toast(`safe-shell blocked: ${hit.reason}`)
 
    return { deny: `safe-shell: blocked (${hit.reason}). Ask the user to run it themselves.` }
  }).catch(($, e, next) =>
    // Fail closed: if the guard itself breaks, refuse instead of letting the command through.
    next.called ? next(e) : { deny: 'safe-shell: the guard failed, so the command was not run.' },
  )
 
  for (const tool of ['Read', 'Edit', 'Write'] as const) {
    on('tool.call', { tool }, ($, e, next) =>
      isSecretEnvFile(e.file_path)
        ? { deny: `safe-shell: ${e.file_path} holds secrets and stays out of the conversation.` }
        : next(e),
    ).catch(($, e, next) =>
      next.called ? next(e) : { deny: 'safe-shell: the guard failed, so the file was not touched.' },
    )
  }
}

Dos detalles importan más que las expresiones regulares.

El guardián decide antes de llamar a next. En cuanto next se ejecuta, el comando ya se ha ejecutado. Un deny devuelto después no deshace nada.

El .catch hace que falle en cerrado. Claude Code omite un hook que lanza una excepción o que supera su límite de 10 segundos, y un guardián omitido deja pasar el comando. El manejador next.called ? next(e) : deny hace lo contrario y rechaza el comando. validate lista cada hook que puede bloquear una llamada e indica si tiene un .catch. Es lo primero que debes revisar en el guardián de otra persona:

❯ ./register.ts hooks: tool.call{tool=Bash}, tool.call{tool=Read|Edit|Write}
❯ ./register.ts gating hook with .catch: tool.call{tool=Bash}
❯ ./register.ts gating hook with .catch: tool.call{tool=Read|Edit|Write}
❯ ./register.ts calls: $.ui.toast
✔ Validation passed

Pruébalo sin una sesión

claude plugin test ejecuta los archivos *.test.ts de un mod contra el motor, sin sesión, sin autenticarte y sin red. El kit de pruebas no tiene nada por debajo de tu mod, así que cada prueba registra hooks sustitutos para todo lo que el mod deja pasar:

import { expect, test } from 'claude-code/testing'
 
const ran = { stdout: 'ran', stderr: '', interrupted: false }
 
test('blocks rm -rf on the home folder', async ($, on) => {
  on('tool.call', { tool: 'Bash' }, () => ({ result: ran }))
  const answer = await $.tool.call({ tool: 'Bash', command: 'rm -rf ~' })
  expect(answer.deny).toContain('recursive delete')
})
 
test('lets ordinary commands through', async ($, on) => {
  on('tool.call', { tool: 'Bash' }, () => ({ result: ran }))
  const answer = await $.tool.call({ tool: 'Bash', command: 'rm -rf ./build && python -m pytest -q' })
  expect(answer.deny).toBeUndefined()
})

El archivo completo tiene cuatro pruebas:

(pass) blocks rm -rf on the home folder [16.23ms]
(pass) blocks a force push to main [7.73ms]
(pass) lets ordinary commands through [7.22ms]
(pass) keeps .env out of Read but allows .env.example [9.40ms]

 4 pass
 0 fail

Mi primera ejecución falló con on("tool.call") after the test first called $. Registra los sustitutos al principio de cada prueba, antes de la primera llamada a $.

Qué pasó en una sesión en vivo

Con la recarga en caliente activada, le pedí a Claude que ejecutara echo "the words git reset --hard inside an echo". El guardián lo rechazó con "blocked (git reset --hard throws away uncommitted work)" y apareció una notificación toast. Ese echo era inofensivo. La coincidencia de patrones no distingue un comando de un texto que lo menciona, y tampoco detecta un comando escondido en una variable, un script o un alias. Los autores japoneses que publicaron guardianes parecidos, como rafi-guard, delete-guard y board-guard, cuentan los dos problemas.

Trata un mod guardián como una segunda red de seguridad. Todo lo que no deba ocurrir nunca va también en una regla deny de permisos. Ten en cuenta también el caso contrario. Según la documentación oficial, en una máquina personal sin configuración gestionada, un mod puede aprobar una llamada que una regla deny rechaza. Los mods no corren en un sandbox. Se ejecutan con tus permisos.

Mod 2: un medidor encima del prompt

Los mods dibujan sobre todo en la franja encima del prompt y en el panel lateral. En el escaneo del catálogo del 6 de octubre, el 41% de los mods dibuja en la franja y el 40% abre un panel. Este mod muestra el tiempo, las llamadas a herramientas y los tokens del último turno, con un botón Hide.

Los valores que lee un dibujo van en $.state, y cada uno se declara en un pequeño archivo de tipos:

// types/index.d.ts, named in plugin.json as "types": "./types/index.d.ts"
export type TurnStats = {
  seconds: number
  tools: number
  inputTokens: number
  outputTokens: number
  model: string | null
}
 
declare module 'claude-code' {
  interface PluginState {
    'turn-meter': { last: TurnStats | null; isHidden: boolean }
  }
}

Los hooks cuentan las llamadas a herramientas, leen durationMs y usage de turn.complete y dibujan la franja:

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'
 
import type { TurnStats } from '../types'
 
const last = atom({ plugin: 'turn-meter', key: 'last' } as const, null)
const isHidden = atom({ plugin: 'turn-meter', key: 'isHidden' } as const, false)
 
const formatTokens = (n: number) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n))
 
export const register: Register = on => {
  let tools = 0
 
  on('prompt.submit', ($, e, next) => {
    tools = 0
    return next(e)
  })
 
  on('tool.call', ($, e, next) => {
    // Count the main conversation's calls; subagents carry an agentId.
    if (e.agentId === undefined) tools += 1
    return next(e)
  })
 
  on('turn.complete', async ($, e, next) => {
    if (e.agentId !== undefined) return next(e)
    const usage = e.usage
    const stats: TurnStats = {
      seconds: Math.round(e.durationMs / 1000),
      tools,
      inputTokens: usage
        ? usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens
        : 0,
      outputTokens: usage?.output_tokens ?? 0,
      model: usage?.model ?? null,
    }
    await update($, last, () => stats)
    if (stats.seconds > 120) $.ui.toast(`That turn took ${stats.seconds}s`)
    return next(e)
  })
 
  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    const stats = await read($, last)
    if (e.props.hasSurvey || stats === null || (await read($, isHidden))) return next(e)
 
    const { Box, Button, Text } = $.ui.resolve(e)
    const model = stats.model === null ? '' : ` · ${stats.model}`
    // The band is shared by every mod: keep what the mods beneath drew, then add a row.
    const below = await next(e)
 
    return (
      <Box flexDirection="column">
        {below}
        <Box>
          <Text dimColor>
            {`Last turn: ${stats.seconds}s · ${stats.tools} tool calls · ${formatTokens(stats.inputTokens)} in / ${formatTokens(stats.outputTokens)} out${model} `}
          </Text>
          <Button key="hide" label="Hide" onPress={() => update($, isHidden, () => true)} />
        </Box>
      </Box>
    )
  })
}

En la prueba, tanto en la terminal como en la app de escritorio, la franja se ve así:

Last turn: 42s · 0 tool calls · 18.2k in / 800 out · claude-opus-5-5 [ Hide ]

Tres cosas que aprendí al crearlo:

  • Guarda los valores que se dibujan en $.state, no en variables del módulo. Una recarga en caliente vuelve a ejecutar register y reinicia tus variables, mientras que $.state sobrevive. /clear empieza de cero, así que, si un valor tiene que sobrevivir a eso, escríbelo en $.store en el evento session.end.
  • La franja es compartida. Llama a next(e) y mete su resultado dentro de tu Box, o esconderás la fila de todos los demás mods. Un autor de note.com perdió una franja por culpa de otro mod antes de entender esto.
  • Los elementos Text no conservan ninguna key. En las pruebas, búscalos por lo que dicen: ui.find({ type: 'Text', text: /Last turn/ }).

Mod 3: /pyenv, para cuando Claude ejecuta el Python equivocado

Busqué mods de ciencia de datos y encontré un hueco. De los 2690 mods del escaneo del 6 de octubre, ninguno menciona Jupyter, pandas, DataFrames, Polars, venv ni conda en su nombre o en su descripción.

El problema que resuelve este mod es habitual cuando trabajas con datos. La herramienta Bash de Claude ejecuta el primer python3 que haya en tu PATH, y a menudo ese no es el entorno de tu proyecto. Esto es lo que imprimió la sonda en mi máquina:

python: /Users/<you>/.local/bin/python3 (3.14.2)
env: none (system or tool-managed Python)
pandas: 3.0.1
numpy: 2.4.2
polars: not installed
matplotlib: 3.11.0
pygwalker: not installed

No había ningún entorno virtual activo, así que un pip install lanzado por Claude habría acabado en un intérprete global.

El comando /pyenv ejecuta esa sonda y responde en dos partes. text es la fila que ves tú. context es una nota que solo lee el modelo, así que las siguientes llamadas de Claude a Bash saben qué intérprete se está usando sin que tengas que escribir otro prompt.

import type { Register } from 'claude-code'
 
// Reads package versions from metadata instead of importing them, so it stays fast.
const PROBE = `
import os, sys
from importlib.metadata import PackageNotFoundError, version
env = os.environ.get("VIRTUAL_ENV") or os.environ.get("CONDA_PREFIX") or "none (system or tool-managed Python)"
print(f"python: {sys.executable} ({sys.version.split()[0]})")
print(f"env: {env}")
for name in ("pandas", "numpy", "polars", "matplotlib", "pygwalker"):
    try:
        print(f"{name}: {version(name)}")
    except PackageNotFoundError:
        print(f"{name}: not installed")
`
 
export const register: Register = on => {
  on('session.start', async ($, e, next) => {
    await $.command.register({
      name: 'pyenv',
      description: "Show which Python, environment and data packages this session's shell uses",
    })
    return next(e)
  })
 
  on('command.run', { command: 'pyenv' }, async $ => {
    try {
      const probe = await $.process.run(['python3', '-c', PROBE], { timeoutMs: 15_000 })
      if (probe.exitCode !== 0) {
        return { text: `pyenv: python3 exited with ${probe.exitCode}: ${probe.stderr.trim().slice(0, 300)}` }
      }
      const report = probe.stdout.trim()
      // `text` is the row you see; `context` is a note only the model reads.
      return {
        text: report,
        context: [`Python environment of this session's shell, from /pyenv:\n${report}`],
      }
    } catch (error) {
      return { text: `pyenv: could not run python3 (${String(error).slice(0, 200)})` }
    }
  })
}

El comando slash de un mod ejecuta tu función al momento, sin turno del modelo, incluso mientras Claude está ocupado. $.process.run recibe una lista de argumentos sin pasar por ninguna shell, y corta a los 30 segundos salvo que definas timeoutMs.

¿Ve $.process.run el mismo PATH que la herramienta Bash de Claude? Un autor de la comunidad descubrió que el node de nvm no aparecía en los procesos de un mod, así que lo comprobé con un mod sonda temporal. En mi sesión de la app de escritorio, un $.process.run simple recibió exactamente el PATH de la herramienta Bash y el mismo python3. Ejecutar la sonda a través de una shell de login, zsh -lc, dio la respuesta equivocada. Apuntaba al Python 3.10.9 de python.org, porque una shell de login no interactiva se salta ~/.zshrc, que es donde casi todo el mundo añade rutas al PATH. Quédate con la llamada simple. Si una lectura te parece rara en tu máquina, compárala con !which python3 escrito en el prompt de Claude Code.

Al probarlo encontré otra trampa. Las llamadas sobre $, como $.process.run y $.command.register, también necesitan sustitutos, y el sustituto de una llamada a $ responde con { value: ... }. Si devuelves el resultado sin envolver, el kit lo rechaza.

Si haces casi todo tu análisis en notebooks y no en scripts, esto se queda corto, porque Claude Code edita los archivos .ipynb como JSON y nunca ve el kernel en vivo. RunCell (opens in a new tab) es un agente para notebooks que ejecuta celdas y lee su salida directamente, así que encaja mejor con ese tipo de trabajo.

Deja que Claude escriba el mod

Nada de esto hay que escribirlo a mano. Pídelo en una sesión, por ejemplo "crea un mod que muestre mi uso del límite de 5 horas encima del prompt". Claude carga la skill integrada plugin-authoring y escribe los archivos en ~/.claude/dev-mods/<session-id>/. El primer archivo provoca una sola pregunta, "Enable hot reloading for this session?". Cuando aceptas, cada cambio se recarga al terminar el turno de Claude. Así se crearon y cargaron los tres mods de arriba.

Lo que añaden la documentación y la comunidad:

  • Copia la carpeta a un lugar permanente. Autores de Qiita y Zenn cuentan que las carpetas de dev-mods se borran junto con las sesiones antiguas pasados unos 30 días.
  • Desarrolla con claude --plugin-dir ./my-mod. Un mod instalado ejecuta una copia en caché, así que editar su carpeta no cambia nada hasta que actualizas el plugin.
  • La app de escritorio no acepta opciones de línea de comandos. Define CLAUDE_CODE_PLUGIN_DIRS en el bloque env de ~/.claude/settings.json. Para esto, Claude Code ignora la configuración del proyecto.
  • Ejecuta /plugin. Una línea en gris como 1 mod active · safe-shell confirma que el mod se cargó.
  • Arranca con claude --debug mientras desarrollas. El log de depuración nombra cada hook omitido y cada dibujo que rechazó el motor.

15 mods de la comunidad que vale la pena estudiar

El catálogo awesome-claude-code-mods (opens in a new tab), con 316 estrellas, escaneó 2690 mods públicos en 1265 repositorios el 6 de octubre, cinco días después del lanzamiento. Sus datos muestran lo que construye la gente:

  • El 68% añade un comando slash
  • El 53% intercepta llamadas a herramientas
  • El 41% dibuja encima del prompt y el 40% abre un panel
  • El 40% lanza procesos, el 12% llama a un modelo y el 11% usa la red

Estos 15 muestran la variedad. Las estrellas corresponden al 7 de octubre de 2026.

ModQué haceEstrellasVale la pena por
terminal-browser (opens in a new tab)Un navegador web junto a la conversación para sitios web, vistas previas de HTML local y pull requests3695El panel más ambicioso hasta ahora
claude-image-view (opens in a new tab)Miniaturas de las imágenes pegadas encima del prompt, en lugar de [Image #1]160Dibujar imágenes en la terminal
hamzafer/claude-code-mods (opens in a new tab)Un conjunto: barra de contexto, guardián de radio de impacto, vista previa de Markdown y dashboard de subagentes138Varios tipos de mod en un solo repo
claude-auto-handoff (opens in a new tab)Pasa una sesión larga a una nueva con un resumen estructurado60La gestión del contexto
prismantis (opens in a new tab)Respuestas con temas visuales: tablas, código, gráficos con caracteres de caja y texto de derecha a izquierda49Cambiar el estilo de los propios mensajes de Claude
cc-arcade (opens in a new tab)Nueve juegos encima del prompt que se pausan cuando Claude termina41Animación con un reloj de fotogramas
jev-permission-gate (opens in a new tab)Un modelo de decisión de Jev evalúa las llamadas a herramientas en auto mode28Una sección de privacidad cuidadosa: envía tus tres últimos mensajes, de hasta 1500 caracteres cada uno, a la API de TypeSafe
prompt-rail (opens in a new tab)Una barra con tus prompts anteriores; pasa el cursor por encima para leerlos y haz clic para saltar a ellos22La pieza central de la reseña japonesa de mods con más likes en Zenn
claude-paste-view (opens in a new tab)Vista previa de las imágenes y los textos largos que pegas18Código pequeño y fácil de leer
claude-gfm-render (opens in a new tab)Alertas de GitHub, listas de tareas y Mermaid en las respuestas, con caracteres de caja o en SVG14Dibujar distinto según la superficie
claude_qamods (opens in a new tab)qa-guide explica en un panel lateral las preguntas y opciones de Claude10Mejorar un diálogo integrado
CC-Usage-Band (opens in a new tab)Límites de 5 horas y de 7 días, contexto y tasa de aciertos de caché encima del prompt10El tipo de mod más común: un medidor
harness-scope (opens in a new tab)Perfiles por repo que ocultan skills, agentes y reglas globales1El autor midió cómo la lista de skills pasaba de 98 entradas a 49
touch-map (opens in a new tab)Muestra qué archivos listó, leyó, editó o creó Claude0Su autor vio que Claude tocó 125 de 304 archivos durante el análisis previo a una refactorización
claude-mods-router (opens in a new tab)Asigna a cada prompt un nivel de esfuerzo con $.model.classify0Su artículo en Qiita (26 likes) explica que cambiar de modelo rompe la caché de prompts

Anthropic también publica tres ejemplos en claude-code-playground (opens in a new tab): token-weather, blast-radius y replay-theater. El código fuente de los mods integrados, entre ellos /diff, está en el repositorio claude-code (opens in a new tab).

Buenas prácticas en las que coincide la comunidad

En la primera semana, solo los autores japoneses publicaron unos 50 artículos con mods que funcionan, mediciones e informes de fallos. Estos son los hábitos que se repitieron una y otra vez.

  1. Lee un mod antes de instalarlo. Clónalo, ejecuta claude plugin validate y lee las líneas hooks: y calls:. Lee el código fuente allí donde aparezcan $.process, $.http, $.fs.write, $.env.get o $.model. Vuelve a validarlo después de cada actualización. Pasar la validación significa que el motor puede cargar el código, no que el código sea seguro.
  2. Los guardianes deciden antes de next y fallan en cerrado. Los mods que solo observan deberían fallar en abierto con .catch(($, e, next) => next(e)), para que un bug en un medidor nunca bloquee tu trabajo.
  3. Espera dentro de las llamadas a $. Un hook tiene 10 segundos de tiempo propio. Un $.process.run o un $.ui.ask en curso no descuenta de ese tiempo, pero tus propias promesas sí.
  4. Nunca guardes ni desestructures $. Pásalo solo a funciones declaradas en el nivel superior del archivo. Varios autores tenían mods que fallaban al cargar sin ningún aviso hasta que corrigieron esto.
  5. Cuida la caché de prompts. Cambiar de modelo a mitad de sesión, o cambiar el system prompt entre peticiones, invalida la caché.
  6. Decide según e.surface. Svg solo dibuja en la app de escritorio. Raster e Image solo dibujan en la terminal.
  7. Fija una versión si trabajas en equipo. La API está en acceso anticipado y cambia de una versión a otra. Cinco de las seis versiones posteriores al lanzamiento, de la 2.1.288 a la 2.1.293, cambiaron o corrigieron el comportamiento de los mods.

El caso de los secretos enmascarados sirve de advertencia. Un autor de note.com creó un mod que sustituía las API keys en la salida de las herramientas antes de que Claude las viera. Aun así, Claude imprimió los valores reales mientras depuraba una edición fallida con od -c, y un Write del archivo completo guardó los marcadores de posición encima de las claves reales. Solo aguantó una regla deny de permisos sobre el archivo. Un mod que cambia lo que muestra la pantalla no le oculta nada al modelo.

Problemas comunes

SíntomaCausa probableSolución
/plugin no lista el modCarpeta que no es de confianza, --safe-mode, --bare o disableAllHooksMarca la carpeta como de confianza y reinicia sin esa opción
hooks modules are turned off in this processAnthropic desactivó en remoto los mods instaladosNo hay nada que cambiar en local. Actualiza, porque la 2.1.289 y la 2.1.290 corrigieron dos casos en los que los mods seguían desactivados
validate falla con "expected record, received undefined"Una CLI antigua aparece antes en tu PATHEjecuta claude --version y actualiza
Un hook se omite sin ningún error visibleLanzó una excepción, por ejemplo al llamar a $.plugin.name(), que es una propiedadEjecuta claude --debug y lee la línea que explica la omisión
Un panel nunca se abreUn panel que se abre sin que lo pidas espera a que haya 144 columnas, solo se acopla junto a la transcripción en el diseño de pantalla completa, y /diff puede taparloEnsancha la ventana, prueba el diseño de pantalla completa y cierra /diff
Funciona en la terminal pero no en la app de escritorioLa app de escritorio incluye su propia versión, y algunos elementos son exclusivos de la terminalRevisa /status en la pestaña Code y decide según e.surface
No aparece la interfaz del mod en VS Code ni en claude -pEsas superficies no dibujanRecurre a una línea en la transcripción o al texto de un comando
Una prueba falla con "nothing beneath the plugins answers"El kit de pruebas no tiene motorAñade hooks sustitutos y responde a las llamadas a $ con { value }

Comparte tu mod

Pon un marketplace.json junto a plugin.json, dentro de .claude-plugin/:

{
  "name": "my-mods",
  "owner": { "name": "you" },
  "plugins": [{ "name": "safe-shell", "source": "./" }]
}

Sube la carpeta a GitHub. A partir de ahí, cualquiera puede instalar el mod desde una sesión de terminal:

/plugin install safe-shell --marketplace you/safe-shell

En la app de escritorio, la ruta es +, luego Plugins y luego Add plugin, en sesiones locales y SSH. Si añades el topic claude-code-mod al repositorio, el catálogo de la comunidad suele incluirlo pocas horas después de tu siguiente push.

Preguntas frecuentes

Guías relacionadas