Hooks de Cursor: guía completa y por qué a veces no se disparan
Los hooks de Cursor permiten que un script observe o bloquee lo que hace el agente: cada comando de shell, cada llamada MCP, cada lectura y edición de archivos. Son potentes y fallan en silencio. Aquí está todo lo que aprendimos al configurarlos de verdad.
RESPUESTA CORTA
Coloca un hooks.json con "version": 1 en ~/.cursor/ (todos los proyectos) o en .cursor/ dentro de un repositorio, asigna un comando a cada evento e imprime JSON en stdout. Usa beforeShellExecution y beforeMCPExecution para permitir, denegar o preguntar, y establece failClosed: true si un hook roto debe bloquear en lugar de dejar pasar el comando. Cuando parezca que se ignora un hook, abre primero el canal de salida Hooks: la mayoría de los problemas son una ruta, un espacio de trabajo que no es de confianza, un JSON no válido o un evento que la CLI de Cursor todavía no envía.
Qué son los hooks de Cursor
Un hook es un comando que Cursor ejecuta en un punto fijo del ciclo del agente. Cursor envía a la entrada estándar del comando un objeto JSON que describe lo que está a punto de ocurrir, espera y lee una respuesta JSON de su salida estándar. Algunos hooks solo observan (registran la edición, formatean el archivo). Otros deciden: un hook beforeShellExecution puede dejar que un comando se ejecute, detenerlo o preguntarte primero.
Eso convierte a los hooks en la herramienta adecuada para tres tareas: protecciones (no ejecutar nunca rm -rf ni git push --force sin una persona), automatización (formatear o pasar el linter después de cada edición) y visibilidad (registrar o reenviar lo que hace el agente para saber cuándo te necesita).
Dónde está hooks.json
Cursor lee hooks de cuatro lugares y los ejecuta todos. Se ejecuta cada hook que coincida, venga de la fuente que venga, y Cursor combina sus respuestas.
| Nivel | Archivo | Notas |
|---|---|---|
| Usuario | ~/.cursor/hooks.json | Se aplica a todos los proyectos. Los comandos relativos se resuelven desde ~/.cursor/. |
| Proyecto | <project>/.cursor/hooks.json | Se sube al repositorio. Solo se ejecuta en un espacio de trabajo de confianza. Los comandos relativos se resuelven desde la raíz del proyecto. |
| Equipo | Panel web de Cursor | Solo Enterprise; se sincroniza con todos los miembros. |
| Empresa | C:\ProgramData\Cursor\hooks.json (Windows), /Library/Application Support/Cursor/hooks.json (macOS), /etc/cursor/hooks.json (Linux, WSL) | Lo gestiona TI. |
Cuando las respuestas no coinciden, gana la más estricta: un deny de cualquier fuente gana a ask, y ask gana a allow. El archivo mínimo que funciona tiene este aspecto (es el ejemplo de la documentación de Cursor):
version debe ser 1. Cursor vigila el archivo y lo recarga al guardarlo. Si un cambio no surte efecto, reinicia Cursor. Solo con eso se resuelve un número sorprendente de informes del foro.
Los eventos en los que puedes usar hooks
La lista actual de la documentación de Cursor, agrupada por su finalidad:
| Grupo | Eventos | ¿Puede bloquear? |
|---|---|---|
| Shell y MCP | beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution | Los dos hooks before…: allow, deny o ask |
| Cualquier herramienta | preToolUse, postToolUse, postToolUseFailure | preToolUse: allow o deny (ask no se aplica hoy) |
| Archivos | beforeReadFile, afterFileEdit | beforeReadFile: allow o deny |
| Prompt | beforeSubmitPrompt | Sí, con continue: false |
| Ciclo del agente | stop, afterAgentResponse, afterAgentThought, preCompact | No (stop puede enviar un mensaje de seguimiento) |
| Subagentes | subagentStart, subagentStop | subagentStart: allow o deny (ask cuenta como deny) |
| Sesión y aplicación | sessionStart, sessionEnd, workspaceOpen | No |
| Autocompletado con Tab | beforeTabFileRead, afterTabFileEdit | beforeTabFileRead: allow o deny |
Cada evento recibe unos cuantos campos comunes (conversation_id, generation_id, model, hook_event_name, cursor_version, workspace_roots) más los suyos propios. beforeShellExecution recibe el command completo y su cwd; stop recibe un status que puede ser completed, aborted o error.
Permitir, denegar y preguntar
Un hook que decide imprime un objeto JSON. Para el hook de shell, tiene este aspecto:
permissionesallow,denyoask.user_messagees lo que ves en Cursor.agent_messagees lo que se le dice al agente, para que cambie de rumbo en lugar de reintentar el mismo comando.
Los códigos de salida importan tanto como el JSON. Salir con 0 significa “usa mi JSON”. Salir con 2 bloquea la acción, igual que deny. Cualquier otro código de salida se trata como un hook fallido, y la acción sigue adelante, salvo que establezcas failClosed. En los hooks de permisos, un JSON no válido o que no se ajusta al esquema bloquea la acción.
La documentación de Cursor indica que ask es compatible con beforeShellExecution y beforeMCPExecution. En marzo de 2026, personal de Cursor escribió en el foro que los hooks solo aplicaban deny y que allow y ask se “basically ignored” (prácticamente se ignoraban), sobre todo con la lista de comandos permitidos o el sandbox activados. No hemos encontrado ningún anuncio de corrección. Si una protección tiene que funcionar sí o sí, devuelve deny, no ask, y pruébala en tu propia versión.
failClosed y timeout
Cada entrada de hook puede llevar opciones junto a command:
failClosed: trueconvierte un fallo, un tiempo de espera agotado, una salida distinta de cero o una salida vacía en un bloqueo. El valor predeterminado esfalse: una protección rota lo deja pasar todo sin avisar. Para un hook de seguridad, casi siempre quierestrue.timeoutse expresa en segundos. Deja margen suficiente si tu hook espera a una persona: alguien que lee un diff necesita más de cinco segundos.matcherfiltra cuándo se ejecuta el hook. En los hooks de shell se compara con la cadena de comando completa; en los hooks de herramientas, con el tipo de herramienta, comoShell,Read,WriteoMCP:<tool_name>.
Hay una contrapartida. En julio de 2026, un usuario de macOS se topó con “MainThreadShellExec not initialized”: el host de shell del propio Cursor no estaba listo y, como el hook era fail-closed (bloquea si falla), la shell del agente quedó bloqueada. El personal de Cursor dijo que la corrección ya se había publicado y recomendó mantener failClosed: true. Si ves ese error en una compilación antigua, la solución temporal era “Developer: Reload Window”.
Hooks en Windows
En Windows, la app de escritorio de Cursor ejecuta los comandos de los hooks a través de PowerShell y les pasa el JSON por una tubería. Un script de PowerShell lo lee de $input; cualquier otro programa lee la entrada estándar como siempre. Hay tres cosas en las que mucha gente tropieza:
- Los scripts de bash no funcionan tal cual. Un
guard.shde un compañero con macOS falla en PowerShell. Usa un script.ps1, un programa compilado o llama a tu intérprete explícitamente (python .cursor/hooks/guard.py). Un hilo del foro de enero de 2026 muestra una configuración con Git Bash que falla consyntax error near unexpected token '[Convert]::FromBase64String', es decir, código de PowerShell que llega a bash. - Directiva de ejecución. Si PowerShell se niega a ejecutar scripts, inícialos con
powershell -NoProfile -ExecutionPolicy Bypass -File …, imprime el JSON y termina conexit 0. - Los programas GUI vuelven demasiado pronto. Este nos costó un día. Si tu hook es un programa de Windows compilado para el subsistema GUI, PowerShell lo inicia y sigue adelante sin esperar. Cursor lee una salida vacía, así que el hook parece no haber hecho nada (o, con
failClosed, lo bloquea todo). Compila el hook como programa de consola, o envuélvelo para que PowerShell espere a que termine.
Los hooks de proyecto también necesitan un espacio de trabajo de confianza. Un miembro del personal de Cursor enumeró qué revisar cuando no hacen nada: la línea “Hook N produced no output” en el log de Hooks, que Settings → Hooks muestre el espacio de trabajo como de confianza, y errores como spawn, ENOENT, EPERM o ExecutionPolicy en la consola.
Hooks en la CLI de Cursor
La CLI de Cursor (cursor-agent) lee los mismos ~/.cursor/hooks.json y .cursor/hooks.json, pero nunca ha enviado todos los eventos que envía el editor. En enero de 2026 solo se disparaban los dos hooks de shell. En abril, el personal de Cursor confirmó que beforeShellExecution, afterShellExecution, afterFileEdit, postToolUse, stop y sessionStart funcionaban en la CLI, mientras que afterAgentResponse y afterAgentThought seguían sin hacerlo (se volvió a confirmar en mayo). Con cualquier evento que no esté en esa lista, haz pruebas antes de confiar en él.
Los agentes en la nube son aún más estrictos: allí no se ejecutan en absoluto los hooks de usuario de ~/.cursor/hooks.json.
Cuando un hook no se dispara: lista de comprobación
- Abre el canal de salida Hooks (View → Output → Hooks) o la pestaña Hooks en Customize. Muestra qué hooks se cargaron, cada ejecución y los errores.
- Revisa el propio archivo JSON. Que esté
"version": 1, que los nombres de los eventos estén escritos exactamente (distinguen mayúsculas de minúsculas) y que el JSON sea válido. - Revisa la ruta. Los comandos relativos se resuelven desde la raíz del proyecto en los hooks de proyecto y desde
~/.cursor/en los hooks de usuario. - Confía en el espacio de trabajo. Los hooks de proyecto no se ejecutan en una carpeta que no es de confianza.
- Ejecuta el hook a mano. Pásale un JSON de ejemplo por una tubería y mira stdout. Cualquier cosa que no sea un único objeto JSON (un
echoperdido, una advertencia) rompe el análisis. - Reinicia Cursor. Al guardar se recarga el archivo, pero varios informes del foro solo se resolvieron con un reinicio completo.
- Conoce las limitaciones. Se ha informado de que
askyallowse ignoran junto a la lista de permitidos y el sandbox; de quesubagentStart/subagentStopno se disparan en algunas versiones; y la CLI omite algunos eventos. Los commits desde el panel Source Control integrado no se interceptan: no existe un evento de hook pre-commit.
Hooks, ejecución automática y Auto-review
Los hooks son independientes de los ajustes de aprobación del propio Cursor (Settings → Agents → Approvals & Execution). La ejecución automática y Auto-review deciden si Cursor te pregunta en su panel; tu hook añade su propia decisión encima. Por eso es tan habitual el informe de “mi hook dijo ask, pero el comando se ejecutó sin más”: otra vía de permisos ya lo había permitido.
Otra cosa que aprendimos por las malas: con Auto-review, Cursor puede preguntar por algunos comandos en su propia ventana. Un hook nunca ve esa pregunta. Ocurre dentro de Cursor, no en la cadena de hooks.
Dónde encaja Pet Island
Si lo que quieres de los hooks es “muéstrame cada comando arriesgado y déjame decidir sin cambiar de ventana”, eso es lo que hace Pet Island con Cursor. Instala los hooks por ti (beforeShellExecution y beforeMCPExecution, con failClosed activado para que un asistente caído nunca deje pasar un comando) y muestra cada solicitud en una pequeña isla en la parte superior de la pantalla con el comando completo. La permites o la deniegas allí; el agente de Cursor espera.
Los comandos y las ediciones de archivos del editor de Cursor funcionan desde la isla. Las preguntas y los planes aparecen en la isla, y los respondes en Cursor. En la CLI de Cursor, los comandos funcionan desde la isla; los demás eventos dependen de lo que envíe la CLI. Al desinstalar solo se eliminan las entradas que añadió Pet Island; tus propios hooks se quedan.
PREGUNTAS FRECUENTES
¿Funcionan los hooks de Cursor en Windows?
Sí. La app de escritorio los ejecuta a través de PowerShell y pasa el JSON por la entrada estándar ($input en un script de PowerShell). Los scripts de bash escritos para macOS necesitan una versión en PowerShell o Python, y los programas del subsistema GUI deben envolverse para que PowerShell espere a que terminen.
¿Por qué mi hook devuelve ask pero el comando se ejecuta igualmente?
El personal de Cursor dijo en marzo de 2026 que los hooks solo aplicaban de forma fiable deny; allow y ask podían quedar anulados por la lista de permitidos o el sandbox. Usa deny para todo lo que no deba ejecutarse, y revisa el canal de salida Hooks para ver qué devolvió realmente tu hook.
¿Qué hace failClosed?
Con failClosed en true, un fallo, un tiempo de espera agotado, una salida distinta de cero o una salida vacía bloquean la acción. Sin él, un hook roto deja que la acción siga adelante.
¿Dónde veo los errores de los hooks?
En el canal de salida Hooks (View → Output → Hooks) y en la pestaña Hooks de Customize.
¿Funcionan los hooks en la CLI de Cursor?
En parte. La CLI lee los mismos archivos hooks.json. En mayo de 2026, el personal de Cursor confirmó los hooks de shell, afterFileEdit, postToolUse, stop y sessionStart, pero no afterAgentResponse ni afterAgentThought.
¿Puede un hook detener un git commit hecho desde el panel Source Control?
No. Cursor no tiene un evento de hook pre-commit, y los commits hechos desde la interfaz de Source Control integrada no se interceptan. Para eso, usa un hook pre-commit normal de git.
Fuentes
Cada ajuste y comportamiento de esta página se comprobó con estas páginas el 26 de octubre de 2026. Los agentes cambian rápido; si algo ya no coincide, manda la página oficial.
- Documentación de Cursor: Hooks
- Foro de Cursor: Los hooks de Cursor no se ejecutaron como se esperaba (personal: solo se aplica deny)
- Foro de Cursor: allow/ask de beforeShellExecution ignorados, la lista de permitidos tiene prioridad
- Foro de Cursor: Hook fail-closed y MainThreadShellExec not initialized
- Foro de Cursor: Un JSON mal formado de un hook permitió el comando en silencio (corregido)
- Foro de Cursor: La CLI de Cursor no envía todos los eventos de hooks
- Foro de Cursor: Los hooks de proyecto no se ejecutan en Windows
- Foro de Cursor: Los hooks de proyecto fallan en Windows con Git Bash
- Foro de Cursor: subagentStart y subagentStop nunca se disparan