Plantilla · Intermedio · 15 min
Plantilla de proyecto para Claude Code: repo listo y seguro
Repo plantilla para Claude Code: CLAUDE.md corto, settings.json con allow, ask y deny, hooks de seguridad y de tests, dos skills, revisor y CI. Probado.
Probado con Claude Code 2.1.316 · revisado
Ver los archivos en GitHub Gratis · licencia MIT
Qué incluye
Un repositorio plantilla para empezar cualquier proyecto con Claude Code bien configurado: un CLAUDE.md corto con lo que Claude no puede deducir, un .claude/settings.json con reglas allow, ask y deny y el modo bypass bloqueado, dos hooks (uno frena secretos, rm -rf amplios y push forzado a main; otro no deja terminar con los tests rotos), dos skills, un subagente revisor, un CI que Claude debe pasar y un .worktreeinclude para sesiones en paralelo. Cada archivo está completo y probado en Linux con Claude Code 2.1.316. También explica qué se aplica antes y después de aceptar la confianza en la carpeta y qué no protege la plantilla.
Son 13 archivos y ninguna dependencia externa (Python 3 y git):
CLAUDE.md instrucciones del proyecto (corto)
.claude/settings.json permisos y hooks del equipo (va en git)
.claude/hooks/proteger.py PreToolUse: secretos, rm -rf, push --force
.claude/hooks/verificar.sh Stop: no termina con tests rotos
.claude/skills/probar/SKILL.md /probar: tests con evidencia
.claude/skills/preparar-pr/SKILL.md /preparar-pr: revisión antes del PR
.claude/agents/revisor.md subagente revisor de solo lectura
.github/workflows/ci.yml el CI que Claude debe pasar
.worktreeinclude qué archivos ignorados copiar a cada worktree
.gitignore .env.example src/app/__init__.py tests/test_app.py
La regla que ordena todo viene de la guía oficial de buenas prácticas: CLAUDE.md es para orientaciones y Claude puede decidir que una no aplica; lo que tiene que cumplirse siempre va en un hook, una regla deny o un ajuste administrado, porque eso lo aplica Claude Code y no el modelo. Por eso «nunca leas .env» aparece tres veces: como frase en CLAUDE.md (para que Claude no lo intente), como regla deny (bloquea la herramienta Read y los comandos de lectura que Claude Code reconoce) y en el hook (bloquea también un cp .env ... en Bash). Lo personal queda fuera de git: .claude/settings.local.json, CLAUDE.local.md y .claude/worktrees/ están en el .gitignore.
Cómo usarla
1. Copia la plantilla en tu repo
Usa el repositorio ctrlia-plantilla-claude-code como plantilla en GitHub (botón «Use this template») o copia sus archivos en tu proyecto. Borra src/ y tests/ de ejemplo.
2. Pon tu comando de tests
Cambia el comando de tests por el tuyo (npm test, pytest, go test ./...) en CLAUDE.md, settings.json, verificar.sh, las skills y ci.yml. En Node, el allow sería Bash(npm test *) y el hook Stop npm test --silent.
3. Valida el JSON
python3 -m json.tool .claude/settings.json
Con claude doctor verás «Invalid settings» si hay un error de sintaxis.
4. Acepta la confianza en la carpeta
Abre claude en modo interactivo una vez y acepta el diálogo de confianza: es el que activa tus reglas allow. Hasta entonces, claude -p avisa en stderr:
Ignoring 4 permissions.allow entries from .claude/settings.json: this workspace has not been trusted.
5. Comprueba que todo cargó
En la sesión: /hooks (deben salir PreToolUse y Stop), /permissions (cada regla y de qué archivo viene), /skills (probar y preparar-pr) y /context (cuánto ocupa todo).
6. Haz una prueba de fuego
Pide «lee el .env» (debe bloquearse) y «cambia sumar para que reste y termina» (el hook Stop debe devolverle el test roto). Los hooks también se pueden probar sin abrir Claude; esta es la salida real del 1-nov-2026:
H=.claude/hooks/proteger.py
echo '{"tool_name":"Read","tool_input":{"file_path":"/x/proyecto/.env"}}' | python3 $H; echo "exit $?"
# Bloqueado por el hook de seguridad: '/x/proyecto/.env' tiene secretos. Pide al usuario el dato que necesites.
# exit 2
echo '{"tool_name":"Read","tool_input":{"file_path":"/x/proyecto/.env.example"}}' | python3 $H; echo "exit $?"
# exit 0
echo '{"tool_name":"Bash","tool_input":{"command":"rm -r -f /"}}' | python3 $H; echo "exit $?"
# Bloqueado por el hook de seguridad: 'rm -rf /' es demasiado amplio. Borra rutas concretas del proyecto.
# exit 2
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf build dist"}}' | python3 $H; echo "exit $?"
# exit 0
echo '{"tool_name":"Bash","tool_input":{"command":"git push --force origin main"}}' | python3 $H; echo "exit $?"
# Bloqueado por el hook de seguridad: 'git push --force' a main está prohibido. Usa una rama y un pull request.
# exit 2
# Hook Stop con un test roto (sumar devuelve a - b):
echo '{"stop_hook_active": false}' | CLAUDE_PROJECT_DIR=$PWD bash .claude/hooks/verificar.sh; echo "exit $?"
# Los tests fallan; arréglalos antes de terminar:
# FAIL: test_suma_enteros (test_app.TestSumar.test_suma_enteros)
# AssertionError: -1 != 5
# exit 2
7. Sube todo y protege main
Sube todo menos lo personal y activa en GitHub la protección de la rama main exigiendo que pase el check «tests» y una revisión.
Los archivos
CLAUDE.md: solo lo que Claude no puede ver
La documentación oficial propone una prueba antes de escribir cada línea: ¿podría Claude encontrarlo leyendo el código? ¿Iría a buscarlo? Si las dos respuestas son sí, sobra. Claude descubre solo la estructura de carpetas, el framework de tests y las convenciones; lo que no descubre son las decisiones del equipo, el proceso de despliegue o qué variables de entorno hacen falta.
Por eso el CLAUDE.md de la plantilla tiene tres bloques y menos de 20 líneas: los comandos exactos (la línea más útil de un CLAUDE.md, según la guía oficial, es la que ejecuta los tests), cómo trabaja el equipo y lo que no está en el código. No hay mayúsculas ni «IMPORTANTE»: la guía oficial dice que los modelos actuales siguen una línea escrita con normalidad y que el énfasis hace que apliquen la regla de más. Si ya tienes un AGENTS.md para Codex o Cursor, desde la 2.1.277 Claude Code lo lee cuando no hay CLAUDE.md; si existen los dos, solo carga CLAUDE.md, así que puedes importarlo con @AGENTS.md. Adáptalo con /init, que sugiere mejoras si el archivo ya existe en lugar de sobrescribirlo. Más en un CLAUDE.md que funciona.
# Proyecto: app de ejemplo (plantilla Ctrl+IA)
## Comandos
- Tests: `python3 -m unittest discover -s tests -v`
- Un solo test: `python3 -m unittest tests.test_app -v`
- No hay paso de build: es Python puro, sin dependencias externas.
## Cómo trabajamos
- Antes de dar algo por terminado, ejecuta los tests y pega el comando y su salida.
- Cambios pequeños: una tarea, una rama, un pull request.
- No añadas dependencias nuevas sin preguntar primero.
- No toques `.github/workflows/` ni `.claude/` salvo que te lo pida.
## Lo que no está en el código
- Los secretos van en `.env` (no está en git). Usa `.env.example` para saber qué variables existen; nunca leas ni copies `.env`.
- `main` está protegida: solo se fusiona por pull request con el CI en verde.
.claude/settings.json: permisos del equipo
Las reglas se evalúan en el orden deny → ask → allow y gana la primera que coincide, aunque otra sea más específica. La plantilla:
- allow: los tests (
Bash(python3 -m unittest *)),git add,git commitygit switch -c. Los comandos de solo lectura comols,cat,git statusogit diffno hace falta permitirlos: Claude Code ya los ejecuta sin preguntar. - ask:
git push, instalar paquetes (pip install,uv add) y editar.github/. Una regla ask pregunta aunque haya un allow que coincida, incluso en el modo auto. - deny: leer
.envy.env.*(conRead(!.env.example)para dejar leer la plantilla de variables),secrets/, ycurl,wgetysudo. - disableBypassPermissionsMode: "disable": impide arrancar en bypassPermissions. Según la documentación funciona desde cualquier ámbito. Quítalo si trabajas dentro de un contenedor desechable.
Dos límites que hay que conocer. Uno: las reglas allow de este archivo no se aplican hasta que aceptas el diálogo de confianza de la carpeta; las deny y ask se aplican desde el principio. Dos: deny: Bash(curl *) compara el texto, así que no frena /usr/bin/curl ni sh -c 'curl …'. Si necesitas cortar la red de verdad, activa el sandbox (/sandbox; en Linux necesita bubblewrap y socat) con sandbox.network.allowedDomains. La plantilla no lo activa en el archivo del proyecto porque, si faltan esas dependencias, Claude Code ejecuta los comandos sin sandbox salvo que pongas sandbox.failIfUnavailable. La sintaxis completa está en la receta de permisos.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(python3 -m unittest *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git switch -c *)"
],
"ask": [
"Bash(git push *)",
"Bash(pip install *)",
"Bash(uv add *)",
"Edit(/.github/**)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(!.env.example)",
"Read(./secrets/**)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)"
],
"disableBypassPermissionsMode": "disable"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Read|Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/proteger.py"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/verificar.sh"
}
]
}
]
}
}
.claude/hooks/proteger.py: lo que no puede fallar
Hook PreToolUse: se ejecuta antes de cada Bash, Read, Edit y Write. Si sale con código 2, Claude Code cancela la herramienta y le pasa el motivo a Claude. Bloquea: leer o escribir .env, .env.local, claves .pem, .key, id_rsa e id_ed25519 (deja .env.example); comandos de Bash que nombran esos archivos; rm -rf (también -r -f, -Rf, --recursive --force y con sudo) sobre /, ~, $HOME, ., .., * o rutas absolutas; y git push --force o -f hacia main o master. Si le llega un JSON ilegible, bloquea, para fallar del lado seguro. Es la versión corta de la receta hooks de seguridad, que explica cada expresión regular.
#!/usr/bin/env python3
"""Hook PreToolUse: frena secretos, rm -rf fuera del proyecto y git push --force a main.
Versión corta; la completa y explicada está en la receta «hooks de seguridad» de ctrlia.dev."""
import json
import re
import sys
SECRETO = re.compile(r"(^|/)(\.env(\.[\w-]+)?|[\w.-]+\.pem|[\w.-]+\.key|id_rsa\w*|id_ed25519\w*)$")
PLANTILLA = re.compile(r"(^|/)\.env\.(example|sample|template)$")
SECRETO_EN_COMANDO = re.compile(r"(?<![\w.-])(\.env(\.[\w-]+)?|[\w.-]+\.pem|id_rsa\w*|id_ed25519\w*)(?![\w.-])")
def bloquear(motivo):
print(f"Bloqueado por el hook de seguridad: {motivo}", file=sys.stderr)
sys.exit(2) # 2 = Claude Code cancela la herramienta y le pasa el motivo a Claude
def main():
try:
datos = json.load(sys.stdin)
except ValueError:
bloquear("entrada JSON ilegible; por seguridad no se ejecuta nada")
herramienta = datos.get("tool_name", "")
entrada = datos.get("tool_input", {}) or {}
if herramienta in ("Read", "Edit", "Write"):
ruta = entrada.get("file_path", "")
if SECRETO.search(ruta) and not PLANTILLA.search(ruta):
bloquear(f"'{ruta}' tiene secretos. Pide al usuario el dato que necesites.")
elif herramienta == "Bash":
comando = entrada.get("command", "")
for m in SECRETO_EN_COMANDO.finditer(comando):
if not re.fullmatch(r"\.env\.(example|sample|template)", m.group(1)):
bloquear(f"el comando toca '{m.group(1)}', que tiene secretos.")
for parte in re.split(r"&&|\|\||;|\||\n", comando):
p = parte.split()
if p[:1] == ["sudo"]:
p = p[1:]
opciones = "".join(o.lstrip("-") for o in p[1:] if o.startswith("-") and not o.startswith("--"))
recursivo = "r" in opciones.lower() or "--recursive" in p
forzado = "f" in opciones or "--force" in p
if p[:1] == ["rm"] and recursivo and forzado:
for destino in (x.strip("'\"") for x in p[1:] if not x.startswith("-")):
if destino in (".", "./", "..", "*", "./*") or destino.startswith(("/", "~", "$HOME", "..")):
bloquear(f"'rm -rf {destino}' es demasiado amplio. Borra rutas concretas del proyecto.")
if re.search(r"\bgit\b.*\bpush\b", parte) and re.search(r"\s(--force\S*|-[a-zA-Z]*f)\b", parte) \
and re.search(r"\b(main|master)\b", parte):
bloquear("'git push --force' a main está prohibido. Usa una rama y un pull request.")
sys.exit(0) # sin objeciones: sigue el flujo normal de permisos
if __name__ == "__main__":
main()
.claude/hooks/verificar.sh: no termina con los tests rotos
Hook Stop: se ejecuta cada vez que Claude intenta terminar su turno. Si no hay cambios en el árbol de trabajo, no hace nada. Si los hay, corre los tests; si fallan, devuelve el final de la salida por stderr y sale con 2, y Claude recibe los errores y sigue trabajando. Para no entrar en bucle mira stop_hook_active (vale true cuando Claude ya sigue por culpa de un hook Stop), y además Claude Code corta solo tras 8 bloqueos seguidos sin ninguna herramienta de por medio.
#!/usr/bin/env bash
# Hook Stop: si hay cambios sin probar, Claude no puede terminar hasta que pasen los tests.
set -euo pipefail
entrada=$(cat)
# Si ya estamos en una vuelta provocada por este hook, no insistas (evita bucles).
if grep -q '"stop_hook_active": *true' <<<"$entrada"; then exit 0; fi
cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0
# Sin cambios en el árbol de trabajo no hay nada que comprobar.
if [ -z "$(git status --porcelain 2>/dev/null || true)" ]; then exit 0; fi
if ! salida=$(python3 -m unittest discover -s tests 2>&1); then
printf 'Los tests fallan; arréglalos antes de terminar:\n%s\n' "$salida" | tail -n 40 >&2
exit 2
fi
exit 0
Ojo: según la documentación, en un repositorio que no es tuyo los hooks del proyecto se ejecutan con claude -p aunque nunca hayas aceptado la confianza. Lo comprobamos: un hook SessionStart creó un archivo antes incluso de pedir inicio de sesión. Por eso conviene revisar .claude/ de cualquier repo antes de lanzar Claude en él (lo explica la checklist de permisos).
.claude/skills/probar/SKILL.md
Las skills viven en .claude/skills/<nombre>/SKILL.md y se invocan con /nombre o las carga Claude solo cuando la description encaja. /probar ejecuta los tests y exige pegar la salida real. Lleva allowed-tools: Bash(python3 -m unittest *), que aprueba solo ese comando durante el turno de la skill. Acótalo siempre: según la documentación, la confianza en la carpeta nunca limita el allowed-tools de una skill de proyecto, así que un repo ajeno podría traer una skill con allowed-tools: Bash. Si es tu primera skill, mira cómo crear una.
---
name: probar
description: Ejecuta los tests del proyecto y resume el resultado con la evidencia. Úsala después de cambiar código o cuando el usuario pregunte si algo funciona.
allowed-tools: Bash(python3 -m unittest *)
---
1. Ejecuta `python3 -m unittest discover -s tests -v`.
2. Si todo pasa, responde con el número de tests y la última línea de la salida (por ejemplo `OK`).
3. Si algo falla, copia el nombre del test, el error y el archivo y la línea donde ocurre.
Propón la causa más probable, pero no cambies código si el usuario solo pidió probar.
4. Nunca digas «todo funciona» sin pegar la salida real.
.claude/skills/preparar-pr/SKILL.md
/preparar-pr lleva disable-model-invocation: true, para que solo la lances tú. Resume la rama, corre los tests y busca secretos, dependencias nuevas, archivos fuera de la tarea y tests debilitados; luego redacta la descripción del PR. No hace push.
---
name: preparar-pr
description: Revisa la rama actual antes de abrir un pull request (tests, secretos, dependencias y cambios fuera de alcance) y redacta la descripción del PR.
disable-model-invocation: true
allowed-tools: Bash(git diff *) Bash(git log *) Bash(git status *) Bash(python3 -m unittest *)
---
Prepara el pull request de la rama actual contra `main`.
1. `git log --oneline main..HEAD` y `git diff --stat main...HEAD`: resume qué cambia.
2. Ejecuta los tests: `python3 -m unittest discover -s tests -v`. Si fallan, para y avisa.
3. Revisa `git diff main...HEAD` y señala, con archivo y línea:
- claves, tokens o contraseñas escritos en el código;
- dependencias nuevas o versiones cambiadas;
- archivos que no tienen que ver con la tarea pedida;
- tests borrados, saltados o con aserciones debilitadas.
4. Escribe la descripción del PR: qué cambia, por qué, cómo se probó (con la salida) y qué debe mirar quien revise.
No hagas `git push` ni abras el PR: eso lo hace el usuario.
.claude/agents/revisor.md
El subagente revisor tiene herramientas de lectura y Bash, y un prompt que pide solo problemas de corrección y seguridad, con archivo, línea y cómo comprobarlo, y que omita el estilo. La guía oficial explica por qué sirve: un revisor nuevo no vio el razonamiento que produjo el código y no se deja llevar por él. Úsalo con «usa el subagente revisor sobre git diff main...HEAD», o usa el /code-review integrado, que también corre en un subagente aparte. Más detalle en la checklist antes de fusionar y en la receta de subagentes.
---
name: revisor
description: Revisor de solo lectura. Úsalo para revisar un diff antes de fusionar, con ojos que no vieron cómo se escribió.
tools: Read, Grep, Glob, Bash
model: inherit
---
Eres un revisor de código exigente. No editas archivos.
Revisa el diff que te indiquen (por defecto `git diff main...HEAD`) y busca solo problemas que afecten a la corrección o a la seguridad:
errores de lógica, casos límite sin cubrir, secretos en el código, entradas sin validar, dependencias nuevas sin justificar
y cambios fuera del alcance de la tarea. Omite el estilo.
Para cada hallazgo da archivo, línea, por qué es un problema y cómo comprobarlo. Si no encuentras nada serio, dilo.
.github/workflows/ci.yml: el CI que Claude debe pasar
Corre en cada pull request y en cada push a main, con permissions: contents: read (el token del job no puede escribir en el repo). Hace dos cosas: ejecuta los mismos tests que el hook Stop y falla si hay algún .env versionado (deja pasar .env.example, .sample y .template). Así, lo que Claude ve en local y lo que ve GitHub es lo mismo: si la rama no está en verde, no se fusiona. Si quieres que Claude revise además cada PR en GitHub, añade la receta de revisión de PRs con GitHub Actions.
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Tests
run: python3 -m unittest discover -s tests -v
- name: Ningún .env en git
run: |
if git ls-files | grep -E '(^|/)\.env(\.[^/]*)?$' | grep -vE '\.env\.(example|sample|template)$'; then
echo "Hay un archivo .env versionado: quítalo del repositorio" >&2
exit 1
fi
.worktreeinclude: el .env en cada sesión paralela
Sirve para trabajar en paralelo con claude --worktree <nombre> (o -w). Cada worktree es un checkout nuevo en .claude/worktrees/<nombre>/ sobre la rama worktree-<nombre>, así que los archivos ignorados como .env no están. El archivo usa sintaxis de .gitignore y solo copia lo que coincide y además está ignorado por git, así que nunca duplica archivos versionados. Lo comprobamos: claude -w prueba creó el worktree y el .env apareció dentro. La documentación recomienda añadir .claude/worktrees/ al .gitignore, y la plantilla ya lo hace. No se aplica a un git worktree add hecho a mano.
.env
.env.local
.gitignore y .env.example
.env
.env.*
!.env.example
.claude/settings.local.json
.claude/worktrees/
CLAUDE.local.md
__pycache__/
# Copia este archivo a .env y rellena los valores (el .env no se sube a git)
API_KEY=
El código y el test de ejemplo
src/app/__init__.py:
def sumar(a, b):
return a + b
tests/test_app.py:
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
from app import sumar # noqa: E402
class TestSumar(unittest.TestCase):
def test_suma_enteros(self):
self.assertEqual(sumar(2, 3), 5)
if __name__ == "__main__":
unittest.main()
Qué NO protege la plantilla
Ser honestos aquí es parte del valor:
- Comandos que no nombran el secreto.
python3 -c "open('.e'+'nv')"o ungrep -r clave .se escapan del hook y de la regla deny. La barrera real a nivel de sistema es el sandbox (lectura confilesystem.denyRead, red conallowedDomains) o un contenedor. - Procesos que no son herramientas de Claude. Hooks, servidores MCP locales, monitores de plugins y la statusline corren fuera del sandbox con todos tus permisos. Un plugin que instales puede traer los suyos (lo cuenta la checklist de plugins).
- Mods. Sin ajustes administrados ni una sesión de Team o Enterprise, un mod instalado puede aprobar una llamada que una regla deny rechaza o que un hook PreToolUse bloqueó, según la documentación de permisos.
- El modo bypassPermissions se salta las preguntas y deja escribir en rutas protegidas como
.gity.claude; por eso la plantilla lo desactiva. - Lo que hace Bash no siempre se puede deshacer con /rewind: solo git lo revierte. Trabaja en ramas y haz commit antes de cada tarea.
- El CI de ejemplo no busca secretos dentro del código ni licencias: para eso hay herramientas específicas o la skill /preparar-pr, que solo señala dónde mirar.
Qué probamos y qué falta
En este PC (Linux, Claude Code 2.1.316), con una configuración aislada para no tocar la de nadie: proteger.py con 20 entradas simuladas (secretos, .env.example, rm -rf en varias formas, push forzado a main y a otra rama, JSON roto); verificar.sh sin cambios (exit 0), con un test roto (exit 2 y la salida del fallo), con stop_hook_active true (exit 0) y con cambios y tests en verde (exit 0); los tests, el YAML del CI y su paso «Ningún .env en git» con y sin un .env forzado en git (exit 1 y exit 0); claude doctor, que no da «Invalid settings» con este settings.json y sí lo da con una coma rota; el aviso «Ignoring 4 permissions.allow entries… not been trusted» con claude -p hasta marcar la carpeta como confiable; y claude -w prueba, que creó el worktree con el .env copiado. Falta probarlo en una sesión con cuenta: que /hooks liste los dos hooks, que Read(!.env.example) deje leer la plantilla y que el hook Stop reenvíe el error a Claude. Cuando lo hagamos, actualizaremos esta página.
Preguntas frecuentes
¿Por qué mis reglas allow no hacen nada en un repo recién clonado?
Porque las reglas allow (y additionalDirectories) del .claude/settings.json del proyecto solo se aplican después de aceptar el diálogo de confianza de la carpeta, que solo aparece en una sesión interactiva. Con claude -p verás en stderr «Ignoring N permissions.allow entries from .claude/settings.json: this workspace has not been trusted». Abre claude una vez en la carpeta y acepta. Las reglas deny y ask se aplican siempre.
¿Puedo poner defaultMode: auto o bypassPermissions en la plantilla?
No tiene efecto. Según la documentación, auto y bypassPermissions puestos en .claude/settings.json o .claude/settings.local.json se ignoran: con auto se usa el modo por defecto de Claude Code y con bypass la sesión arranca en modo Manual. El resto de valores (default, acceptEdits, plan, dontAsk) sí funcionan desde el proyecto en la terminal. La extensión de VS Code nunca lee el modo inicial de los ajustes del proyecto.
¿El hook Stop no deja a Claude en un bucle infinito?
No. El script sale sin bloquear si stop_hook_active es true (Claude ya está continuando por un hook Stop) y, además, Claude Code ignora el bloqueo tras 8 continuaciones seguidas sin llamar a ninguna herramienta. Se puede subir ese límite con CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.
¿Sirve para Node, Go o Java?
Sí. Solo cambia el comando de tests en cinco sitios: CLAUDE.md, la regla allow de settings.json, verificar.sh, las dos skills y ci.yml (y el paso de setup del lenguaje en el CI). Los hooks de seguridad no dependen del lenguaje del proyecto, solo necesitan python3 en la máquina.
¿Por qué el .worktreeinclude no copia mi .env?
Solo copia archivos que coinciden con un patrón y además están ignorados por git, y solo en los worktrees que crea Claude Code (--worktree, subagentes con worktree y sesiones paralelas de la app de escritorio). No actúa con un git worktree add manual ni cuando un hook WorktreeCreate sustituye la creación; en ese caso copia los archivos dentro del hook.
¿Debo subir .claude/settings.json a git?
Sí, es el archivo del equipo. Lo personal va en .claude/settings.local.json (Claude Code guarda ahí lo que apruebas con «no volver a preguntar») y en CLAUDE.local.md, ambos en el .gitignore. Ojo: si settings.local.json está versionado en git, Claude Code lo trata como del repositorio y retiene sus reglas hasta que confíes en la carpeta.