Mods

Una explicación breve sobre qué son los mods, una forma de modificar el comportamiento natural de claude code

1. Qué son

A principios de octubre, Anthropic laznó una feature para Claude Code que, imagino, no tardará en extenderse a otras herramientas agénticas: los mods.

Un mod es esencialmente un plugin de Claude Code cuyo comportamiento está implementado con function hooks en TypeScript/JavaScript. Puede engancharse a eventos internos de Claude Code, observarlos, modificarlos, bloquear acciones e incluso dibujar UI dentro del terminal.

Si no vienes del front esto igual te suena un poco raro, pero para entenderlo puedes pensar en un click del ratón. Cada vez que pulsas algo en una página web se emite un evento (onClick), que puedes capturar para hacer cosas, como navegar a otra página. Entonces, cada vez que Claude Code hace algo, emite un evento, y un mod es básicamente un conjunto de funciones asociadas a eventos; Claude Code llama a la función correspondiente cada vez que ocurre uno.

A diferencia de los hooks clásicos, que son comandos de shell definidos en settings.json, los mods se ejecutan como funciones dentro del propio Claude Code, lo que permite, por ejemplo, modificar la propia interfaz del agente o cambiar el diálogo en el que formula preguntas.

MecanismoPara qué sirve
CLAUDE.md / AGENTS.mdContexto e instrucciones del proyecto
SkillEnseñarle un procedimiento reutilizable
SubagentDelegar trabajo con contexto/herramientas propias
MCPDar acceso a herramientas o sistemas externos
Hook clásicoEjecutar algo al ocurrir un evento
PluginEmpaquetar varias extensiones
ModModificar el comportamiento interno y/o la UI de Claude Code

El límite es la imaginación. Podríamos, por ejemplo:

  • interceptar una llamada a Bash;
  • inspeccionar lo que Claude intenta ejecutar;
  • reescribir una tool call antes de ejecutarla;
  • cancelarla;
  • mantener estado durante la sesión;
  • registrar nuevas herramientas;
  • añadir slash commands;
  • reaccionar a eventos del agente;
  • llamar al modelo;
  • dibujar paneles junto a la conversación;
  • mostrar widgets encima del prompt;
  • instrumentar la sesión.

2. Cómo se preparan

Para entender cómo funcionan podemos hacer un mod muy sencillo que muestre un toast con la frase «Vamos ahí» cada vez que Claude haga un git add. Vamos a llamarlo git-add-mod y podemos guardarlo en cualquier lado, por ejemplo, en .[ruta whatever]\dev-mods. Esta carpeta del mod puede estar en cualquier sitio. Lo único que importa es que la ruta de CLAUDE_CODE_PLUGIN_DIRS que veremos luego apunte a la carpeta que contiene .claude-plugin/plugin.json.

Ahora bien, dónde la pongamos sí cambia algunas cosas:

  • Dentro del propio repo de un proyecto: aparece en git status y se podría subir por error. Además, si cambias el repo de sitio, se rompe la ruta.
  • Fuera de cualquier repo, por ejemplo C:\Users\[USUARIO]\mods\git-add-mod: es lo más limpio para un mod personal que quieres en todas las sesiones y proyectos.
  • ~/.claude/dev-mods/<id-de-sesión>/: es donde la skill escribe por defecto, pero es una carpeta de sesión y puede desaparecer, así que no conviene usarla para algo permanente.

En donde hayamos decidido guardar nuestros mods, preparamos una estructura parecida a esta:

git-add-mod/
├── .claude-plugin/
│   └── plugin.json      ← quién soy
│
└── hooks/
    ├── hooks.json       ← qué módulos/hooks cargo
    └── register.ts      ← qué hago realmente

En plugin.json definimos el manifiesto del plugin. No contiene lógica, su única función es indicarle a Claude que esta carpeta es un plugin, se llama así, tiene esta versión y estos metadatos.

{
  "name": "git-add-mod",
  "version": "0.1.0",
  "description": "Dice Vamos allá antes de git add"
}

En hooks/hooks.json le indicamos a Claude Code qué hooks o módulos debe cargar para ese plugin.

{
  "modules": ["./register.ts"]
}

Y ya en register.ts el código del mod, lo que debe ejecutarse.


import type { On } from "claude-code";

const GIT_ADD = /(^|[;&|]\s*|&&\s*)git\s+add\b/;

export function register(on: On) {
  on("tool.call", async ($, e, next) => {
    if (e.tool === "Bash" && typeof e.command === "string" && GIT_ADD.test(e.command.trim())) {
      $.ui.log("Vamos allá");
    }

    return next(e);
  });
}

Por último, hay que indicarle a Claude dónde están nuestros mods en el archivo settings.json

{
  "autoUpdatesChannel": "latest",
  "tui": "fullscreen",
  "permissions": {
    "allow": [
      "Bash(python3 -m py_compile extraction.py)"
    ]
  },
  "enabledPlugins": {},
  "env": {
    "CLAUDE_CODE_PLUGIN_DIRS": "C:\\Users\\la-ruta-que-sea\\dev-mods\\git-add-mod" ← aquí
  },
  "model": "sonnet"
}

Y con esto ya estaría listo.

Antes de probarlo conviene comprobar la versión:

claude --version

Los mods requieren Claude Code 2.1.287 o posterior. Para probar el mod durante una sesión, sin instalarlo, usamos --plugin-dir:

claude --plugin-dir mods/git-add-mod

Mientras la sesión sigue abierta podemos editar register.ts: Claude Code recarga el módulo al terminar el turno en el que detecta cambios. Cada recarga vuelve a ejecutar register, así que el estado guardado en variables del módulo se reinicia. Si necesitamos conservarlo entre recargas, debemos usar la API de estado de Claude Code.

Para inspeccionar qué ha detectado Claude Code y validar el manifiesto y el módulo:

claude plugin validate mods/git-add-mod

El resultado lista los eventos registrados (hooks), las llamadas a la API (calls) y, si aplica, las lecturas y escrituras de entorno o estado. Es especialmente útil para descubrir un nombre de evento mal escrito o una llamada que el analizador estático no puede detectar.

3. El ciclo de vida de un mod

La forma habitual de trabajar con un mod es:

  1. Crear la estructura del plugin y escribir el módulo de hooks.
  2. Cargarlo con claude --plugin-dir ./git-add-mod.
  3. Probar el comportamiento en la sesión.
  4. Editar el código y aprovechar la recarga en caliente.
  5. Ejecutar claude plugin validate ./git-add-mod antes de compartirlo.

También podemos pedirle a Claude que lo cree. Claude usa la skill interna plugin-authoring y escribe el mod en ~/.claude/dev-mods/<session-id>/<mod-name>/. Al guardar el primer archivo pregunta si queremos habilitar la recarga en caliente. Esa carpeta pertenece a la sesión y puede eliminarse durante la limpieza automática, así que para conservar el mod hay que copiarlo a una ubicación propia y cargarlo con --plugin-dir, o distribuirlo como plugin desde un marketplace.

Un mod generado durante una sesión solo puede cargarse si el workspace es de confianza, alguien puede aprobar su activación y los mods no están deshabilitados mediante --safe-mode, --bare, disableAllHooks o la configuración administrada de la organización.

4. La API de los mods

Cada hook recibe los mismos tres argumentos:

on('tool.call', async ($, e, next) => {
  // $    API de Claude Code
  // e    datos del evento
  // next pasa el evento al siguiente handler y al comportamiento normal
  return next(e)
})

En el primer argumento, $ se encuentran las acciones que permite la api, como:

  • $.ui: escribir mensajes, invalidar y redibujar la interfaz, abrir paneles y reaccionar a botones o campos.
  • $.command: registrar comandos /comando para el usuario y responder a command.run.
  • $.tool: registrar tools para que las use Claude y manejar después sus llamadas.
  • $.model.complete y $.model.fork: pedir una respuesta a un modelo usando una petición independiente o el contexto de la sesión.
  • $.state y $.store: guardar información entre eventos y, según el caso, entre recargas.
  • $.fs, $.process y $.http: acceder al sistema de archivos, ejecutar procesos y hacer peticiones de red.
  • temporizadores y mensajería entre sesiones, cuando el mod necesita ejecutar trabajo entre eventos o coordinar varias sesiones.

Los comandos y tools suelen registrarse en session.start, para que estén disponibles antes del primer prompt. Un comando puede ejecutarse inmediatamente mientras Claude está trabajando si se registra con immediate: true. Una tool registrada por un mod aparece para Claude con un nombre completo como mcp__git-add-mod__nombre-de-la-tool.

La documentación más fiable para los eventos, los tipos de sus datos y los métodos disponibles es la que Claude Code genera dentro del propio mod, en .claude-plugin/types/. Los archivos claude-code/index.d.ts y tsconfig.json permiten que el editor autocomplete y que podamos ejecutar tsc -p . para comprobar los tipos. La API puede cambiar entre versiones, por lo que conviene confiar en esos tipos para la versión instalada.

5. Seguridad y alcance

OJo cuidado, un mod es código que se ejecuta dentro de Claude Code con nuestros permisos. Puede leer y escribir archivos accesibles para nuestro usuario, iniciar procesos, hacer peticiones de red, leer variables de entorno y archivos de configuración, ver los prompts y las tool calls de la sesión, reescribirlos, enviar mensajes a otras sesiones, aprobar herramientas y consumir el uso del plan o de la API.

Los mods no están aislados. El sandboxing de Bash limita los comandos que ejecuta Claude, pero un proceso iniciado por un mod puede quedar fuera de ese aislamiento. Por eso solo debemos instalar mods de autores y marketplaces de confianza; antes de cargar uno conviene revisar su plugin.json, sus hooks y las llamadas que muestra claude plugin validate.

6. Tests automatizados

Los mods también pueden probarse sin iniciar una sesión, autenticarse ni hacer llamadas de red. Creamos archivos cuyo nombre termine en .test.ts e importamos test y expect desde claude-code/testing:

import { expect, test } from 'claude-code/testing'

test('git-add-mod deja pasar la tool call', async ($, on) => {
  on('tool.call', () => ({ result: 'ok' }))

  const result = await $.tool.call({
    tool: 'Bash',
    command: 'git add src/app.ts',
  })

  expect(result.result).toBe('ok')
})

Ejecutamos los tests desde la carpeta del plugin:

claude plugin test

El objeto $ del test simula a Claude Code y dispara eventos como $.tool.call, $.command.run, $.prompt.submit o $.session.start. El segundo argumento, on, permite crear stubs para responder a las llamadas que haría Claude Code: así podemos simular una tool, una lectura del store o una respuesta del modelo sin ejecutar la operación real. Si un test falla, el comando termina con código de salida 1, por lo que puede usarse en CI.

7. Instalar y compartir

Para uso local, --plugin-dir es la opción más cómoda durante el desarrollo. Para instalar un mod como plugin desde un marketplace:

/plugin install nombre-del-mod@nombre-del-marketplace

o desde la shell:

claude plugin install nombre-del-mod@nombre-del-marketplace

Si se instala o actualiza desde la shell mientras una sesión está abierta, podemos ejecutar /reload-plugins. Los plugins instalados se cachean por versión: después de modificar uno hay que incrementar version en plugin.json y reinstalarlo.

La forma rápida de cargar una carpeta durante una sesión sigue siendo --plugin-dir; CLAUDE_CODE_PLUGIN_DIRS puede servir para indicar directorios de plugins en la configuración, pero el directorio debe contener el plugin completo y, dentro de él, .claude-plugin/plugin.json.

Más información en la documentación oficial sobre mods, la guía para crear un mod, la referencia de la API y la guía de tests.