git hooks para coding agents: listar, no instalar ni --no-verify
Resumen
Los hooks de Git son programas en $GIT_DIR/hooks o en core.hooksPath. Sin bit ejecutable, Git los ignora. Un agente lista y respeta el exit code; no instala samples, no pone core.hooksPath=/dev/null y no usa --no-verify. git hook run es para comandos scripted, no para saltarse el hook real. Distinto de git commit y de git config. Git 2.50.1.

Qué resuelve
Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.
Los hooks de Git son programas que Git invoca en puntos fijos de su ejecución. El man (githooks(5); git-scm.com/docs/githooks HTTP 200, last-modified 2026-08-31; man local Git 2.50.1 / Apple Git-155, 2025-07-22): Hooks are programs you can place in a hooks directory to trigger actions at certain points in git’s execution. Hooks that don’t have the executable bit set are ignored. SYNOPSIS: $GIT_DIR/hooks/* o git config core.hooksPath/*.
No es commit. commit crea el objeto; el hook puede abortar ese create. Tampoco es config: core.hooksPath redirige el directorio, no “apaga Git”. Ponerlo en /dev/null (el man de config lo documenta) deshabilita todos los hooks. Un agente no lo toca.
Contrato: listar. Respetar el exit code. Cero instalar samples. Cero chmod +x de *.sample. Cero core.hooksPath=/dev/null. Cero --no-verify / -n. Cero git hook run como sustituto del comando real.
Dónde viven y cuándo Git los ignora
Por default el directorio es $GIT_DIR/hooks (.git/hooks en un clone no-bare). core.hooksPath lo cambia: absoluto o relativo al directorio donde corre el hook (man de git-config(1), sección core.hooksPath, HTTP 200). Relativo no es “relativo al repo root” a ciegas.
Antes de invocar, Git cambia el cwd: en un repo no-bare, a la raíz del working tree; en bare, a $GIT_DIR. Excepción: hooks de push (pre-receive, update, post-receive, post-update, push-to-checkout) siempre corren en $GIT_DIR.
GIT_DIR y GIT_WORK_TREE se exportan para que el hook hable con este repo. Si necesita un repo extranjero, el man exige unset $(git rev-parse --local-env-vars) antes del git -C. Un agente no reescribe hooks ajenos para “arreglar” el cwd.
git init puede copiar samples del template (git-init(1), TEMPLATE DIRECTORY). Un *.sample no es un hook activo: le falta el bit ejecutable y, a menudo, el nombre sin sufijo. Activarlo es del humano, no del agente.
Cliente vs servidor: dos contratos
Los hooks de cliente viven en el clone del humano y disparan commit, merge, rebase, push local. Los de servidor viven en el bare que recibe el push (receive-pack). Mezclarlos es el error clásico: un pre-commit nunca corre en GitHub. La política de “no force-push a main” en el servidor es update / pre-receive o branch protection, no un script en .git/hooks del laptop.
Cliente que un agente sí va a chocar:
| Hook | Quién lo llama | --no-verify | Efecto si exit ≠ 0 |
|---|---|---|---|
pre-commit | git commit | sí | aborta antes de crear el commit |
prepare-commit-msg | git commit | no | aborta; edita el archivo del mensaje |
commit-msg | git commit / git merge | sí | aborta; puede editar el mensaje |
post-commit | git commit | n/a | no cambia el resultado |
pre-push | git push | no (es otro flag) | no empuja nada |
pre-commit no recibe parámetros. El sample aborta trailing whitespace y, con hooks.allownonascii unset o false, filenames no-ASCII. commit-msg recibe un path: el archivo del mensaje. El sample aborta Signed-off-by duplicados.
prepare-commit-msg corre después de armar el mensaje default y antes del editor. El man: it is not suppressed by the --no-verify option y should not be used as a replacement for the pre-commit hook. Un agente que “pasa -n para no molestar” sigue disparando este hook.
pre-push recibe dos args (nombre y URL del remoto) y por stdin líneas local-ref SP local-oid SP remote-ref SP remote-oid LF. Exit ≠ 0 = push aborta sin empujar nada. No es --force.
Servidor: pre-receive una vez por operación (stdin: old-oid new-oid ref); si falla, ningún ref se actualiza. update una vez por ref. post-receive después del trabajo: no puede deshacer. Un coding agent en un clone no instala estos.

--no-verify no es un atajo de agente
El man de pre-commit y de commit-msg: se bypassan con --no-verify. El de commit ya lo prohíbe para agentes. Aquí el matiz operativo:
-n/--no-verifyno apagaprepare-commit-msg.-nno apagapre-push. El push tiene su propio--no-verify(man degit-push): un agente no lo usa.-nno es equivalente acore.hooksPath=/dev/null. El segundo mata todos los hooks del proceso, incluido el que el humano puso para no commitear secretos.- Un hook que falla con exit 1 es una señal. Releer stderr, corregir el index, repetir el comando sin
-n. No “reintentar con verify off”.
core.hooksPath=/dev/null (man de git-config(1)): You can also disable all hooks entirely. Lo marca para expert users y on a per-command basis con git -c core.hooksPath=/dev/null. Un agente no lo pone en --local, no lo exporta, no lo deja en GIT_CONFIG_COUNT.
git hook run no sustituye el comando
git-hook(1) (git-scm.com/docs/git-hook HTTP 200; man local 2025-07-22): A command interface for running git hooks, for use by other scripted git commands. SYNOPSIS: git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>].
Es la API interna para que commit / am / receive-pack disparen el programa. Un agente no la usa para “probar” pre-commit y luego git commit --no-verify, ni para simular pre-push con stdin inventado, ni para invocar update / pre-receive en un clone no-bare.
--ignore-missing quietly returning zero si el hook no existe. Un agente autónomo no lo usa para tragar un miss. --to-stdin=<path> manda un archivo entero al stdin; el path lo arma Git, no un dump del working tree.
Qué hace un agente (y qué no)
Permitido, en este orden:
git config get --show-origin --show-scope -- core.hooksPath
ls -l "$(git rev-parse --git-path hooks)"
Si core.hooksPath está set, lista ese directorio, no .git/hooks. Sin bit x, Git ignora el archivo aunque se llame pre-commit. No lo actives.
Prohibido:
chmod +x .git/hooks/pre-commit.sampleni copiar el sample quitando.sample.- Escribir un hook nuevo en
.git/hookso encore.hooksPath(side effect persistente en la máquina del humano). git config set core.hooksPath /dev/nullnigit -c core.hooksPath=/dev/null commit.git commit --no-verify,git commit -n,git push --no-verify.git hook run pre-receive/update/proc-receivedesde un worktree de feature.- Borrar hooks “porque fallan en CI”. El fallo es el contrato.
Si pre-commit aborta: lee stderr, arregla paths ya staged (add por path, nunca git add .), vuelve a git commit -m. Si el hook exige un linter que no está en el PATH, bloquea y reporta. No lo desinstalas.
post-checkout no deshace el switch: su exit status sí se propaga a git switch / git checkout, pero no revierte HEAD. Un agente no reintenta switch para silenciar un hook de notificación.
Relación con el resto del flujo
pre-rebase puede impedir un rebase. Un agente no lo bypassa. post-rewrite corre después de --amend o rebase. Como amend y force-push ya están vetados, casi no debería dispararse.
reference-transaction solo mira el exit en prepared: ≠ 0 aborta. Un agente no lo instala para auditar refs.
El hub de comparativas y decisiones es el lugar de esta pieza: contrato Git nativo, no tutorial de Husky. Si el repo usa un framework, el humano lo instaló; el agente lo respeta.

Checklist
git config get --show-origin --show-scope -- core.hooksPath— no lo sets.- Lista el directorio real;
*.sampleno es hook. - Sin bit ejecutable = ignorado. No
chmod +x. git commit -mcon index ya staged. Cero-n/--no-verify.- Si el hook falla: stderr → fix → repetir. No apagar hooks.
git hook runno sustituyecommit/push.- Hooks de servidor no se instalan en un clone de feature.
core.hooksPath=/dev/nullestá vetado, incluso one-shot con-c.
FAQ
¿Husky / lefthook / pre-commit.com son lo mismo? No. Instalan un pre-commit (o setean core.hooksPath) que llama su runner. El contrato de Git no cambia. Un agente no npm i husky ni pre-commit install.
¿Puedo versionar los hooks en el repo? Sí, con core.hooksPath a un path del working tree. Setearlo es del humano. El agente, como mucho, lee.
¿git commit -n es más rápido? Salta pre-commit y commit-msg. No salta prepare-commit-msg. Es más ciego. Vetado.
¿Y si el hook se cuelga en la red? Reporta el bloqueo. Un timeout del agente no es --no-verify.
La siguiente lectura práctica es commit (el comando que dispara tres de estos hooks) y el curso si todavía no tienes el loop local.
Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git maintenance para coding agents: housekeeping programado, no gc a pelo

git credential para coding agents: helpers, no tokens en el prompt

git verify-tag para coding agents: ningún release sin firma válida
