Haces git commit y Husky aborta el commit con un mensaje que parece un problema de PATH:
.husky/pre-commit: 6: : not found
husky - pre-commit script failed (code 127)
husky - command not found in PATH=node_modules/.bin:/usr/local/bin:/usr/bin:/bin
Las líneas que añade Husky pueden variar según la versión, pero el detalle importante es el mismo: el comando que "no se encuentra" tiene el nombre vacío (6: : not found). Pasa al usar Git en WSL2 (Ubuntu), Linux o macOS con hooks que se editaron o se descargaron en Windows. Este artículo explica por qué no es un problema de PATH, cómo confirmarlo y cómo evitar que vuelva a ocurrir.
Por qué pasa
Un archivo de texto termina cada línea con LF (\n, Linux y macOS) o con CRLF (\r\n, Windows). El intérprete sh solo reconoce \n como fin de línea, así que el \r sobrante se queda dentro de la línea. En una línea en blanco guardada con CRLF, ese carácter es lo único que hay: sh lo toma como un comando llamado \r, no lo encuentra y responde "not found" con estado de salida 127. El estándar POSIX define 127 como el estado de un comando que no se encuentra.
Por eso el mensaje tiene un nombre vacío entre los dos puntos: el carácter \r no se ve. Según el terminal, incluso puede verse desordenado, porque el retorno de carro mueve el cursor al inicio de la línea. El número (6) es la línea del hook donde sh tropezó con el comando vacío.
Husky ejecuta cada hook con sh -e (el script se detiene en el primer comando que falla) y solo informa del código de salida. En las versiones recientes, cuando ese código es 127 añade además el aviso command not found in PATH. Ese aviso es genérico y desvía el diagnóstico hacia el PATH, aunque la causa sea el formato del archivo.
El CRLF llega al hook por varias vías: un editor en Windows configurado para guardar con CRLF, una herramienta que genera el archivo, o core.autocrlf=true al clonar el repositorio en Windows y luego usarlo desde WSL. Si el comando faltante fuera real, el mensaje mostraría su nombre (por ejemplo pnpm: not found). El nombre vacío apunta al fin de línea.
Solución
-
Confirma que el hook tiene CRLF. Desde la raíz del repositorio, en WSL o Linux:
cat -A .husky/pre-commitCon CRLF, cada línea termina en
^M$; con LF solo aparece$:pnpm lint-staged^M$ ^M$ pnpm exec tsc --noEmit^M$La opción
-Aes de GNU coreutils. En macOS puedes usarfile .husky/pre-commit(muestra "with CRLF line terminators") ogit ls-files --eol .husky(el valorw/crlfindica CRLF en el directorio de trabajo). -
Convierte los hooks a LF. Hazlo con todos los hooks que tengas (
pre-commit,pre-push,commit-msg, etc.):sed -i 's/\r$//' .husky/pre-commit .husky/pre-pushEsa sintaxis de
sed -ies la de GNU (Linux y WSL). En macOS funcionaperl -pi -e 's/\r$//' .husky/pre-commit .husky/pre-push, y en cualquier sistema puedes usardos2unixsi lo tienes instalado. También puedes cambiar el fin de línea desde el editor: en VS Code, haz clic en el indicador CRLF de la barra de estado y elige LF. -
Fija LF con
.gitattributes. Crea o edita el archivo en la raíz del repositorio:.husky/* text eol=lf *.sh text eol=lfSegún la documentación de Git,
eol=lfindica que el archivo se escribe con LF en el directorio de trabajo al hacer checkout, y el atributoeolactivatextsi no estaba definido, de modo que Git también normaliza a LF al guardar en el índice. Solo cuandoeolno está definido Git recurre acore.autocrlfocore.eol. -
Renormaliza lo que ya estaba guardado con CRLF.
git add --renormalize . git status git commit -m "chore: normalize hook line endings to LF"--renormalizevuelve a aplicar el filtro de normalización a todos los archivos rastreados, algo pensado justo para cuando cambias atributos de fin de línea. Actualiza el índice, no el directorio de trabajo: por eso el paso 2 va antes. Revisagit statusantes de confirmar; solo deberían aparecer archivos que coincidan con esos patrones. Si el propio commit falla porque el hook aún tiene CRLF, usagit commit --no-verifysolo para este commit de corrección.
Cómo verificar que quedó resuelto
cat -A .husky/pre-commit
git ls-files --eol .husky
sh -e .husky/pre-commit; echo $?
El primer comando no debe mostrar ^M. El segundo debe mostrar i/lf y w/lf con el atributo text eol=lf para cada hook. El tercero ejecuta el hook como lo hace Husky y debe terminar con 0 si las comprobaciones del hook pasan. Para cerrar, haz un commit y un push de prueba: ya no debe aparecer el código 127.
Variantes y errores relacionados
El mismo error en pre-push y otros hooks
La causa es idéntica en cualquier hook. Convierte todos los archivos de .husky/ que hayas creado, no solo el que falló primero.
Mensajes distintos según el intérprete
Con Bash, el mismo problema se ve como line 6: $'\r': command not found. Si el hook tiene un shebang y se ejecuta directamente, el sistema busca un intérprete llamado sh\r y el error es de este estilo: env: $'sh\r': No such file or directory. También termina con estado 127. En todos los casos la solución es la misma.
Cuando sí es un problema de PATH
Si el mensaje incluye el nombre de un comando real (pnpm: not found, node: not found), el fallo es de entorno. Es habitual con gestores de versiones de Node o con clientes gráficos de Git que no cargan tu configuración de shell. La documentación de Husky tiene una sección sobre gestores de versiones y PATH.
Otros scripts del repositorio
Los archivos .sh, los scripts de entrada de un Dockerfile y cualquier script que ejecute Linux fallan igual con CRLF. La segunda línea de .gitattributes (*.sh text eol=lf) los cubre.
Resumen
.husky/pre-commit: 6: : not foundcon el nombre vacío no es un problema dePATH: es un\rde CRLF queshinterpreta como un comando.- El código 127 significa "comando no encontrado" y el aviso de
PATHque añade Husky es genérico. - Confirma con
cat -A(busca^M$) y corrige consed -i 's/\r$//'o con el selector de fin de línea del editor. - Evita que vuelva con
.husky/* text eol=lfy*.sh text eol=lfen.gitattributes, másgit add --renormalize .. - Verifica con
git ls-files --eol .huskyy un commit y un push reales.
Comentarios
Publicar un comentario