Receta · Intermedio · 10 min
Permisos de Claude Code: deja de aprobar cada comando
Configura modos de permiso y reglas allow, ask y deny en settings.json para que Claude Code no te pregunte por todo, sin abrirle la puerta a lo peligroso.
Probado con Claude Code 2.1.316 · revisado
El problema
Claude Code te pide permiso para casi cada comando: npm test, pytest, git commit... Al final apruebas sin leer, o te pasas a --dangerously-skip-permissions y le das vía libre a todo. Las dos salidas son malas. Lo que quieres es que corra solo lo seguro y repetitivo, que pregunte por lo delicado y que nunca toque tus secretos.
La solución
1. Elige el modo de permiso adecuado
El modo decide qué se aprueba sin preguntarte. Cámbialo con Shift+Tab en la terminal o al arrancar con claude --permission-mode <modo>.
| Modo | Qué corre sin preguntar | Cuándo usarlo |
|---|---|---|
default (en la interfaz se llama «Manual») | Solo lecturas | Proyectos nuevos o código que no conoces |
acceptEdits | Lecturas, ediciones de archivos y comandos de archivos comunes (mkdir, touch, mv, cp) dentro del proyecto | Cuando revisas los cambios con git diff al final |
plan | Lecturas y comandos de solo lectura; no edita tu código | Para explorar y planificar antes de tocar nada |
auto | Todo, pero un clasificador revisa en segundo plano cada comando y petición de red | Sesiones largas con pocas interrupciones |
dontAsk | Lecturas y lo que tengas en allow; lo demás se deniega sin preguntar | Scripts y CI con una lista cerrada de herramientas |
bypassPermissions | Todo (equivale a --dangerously-skip-permissions) | Solo dentro de un contenedor o máquina virtual aislada |
Desde la versión 2.1.283, si tu cuenta y tu modelo admiten el modo auto, es el modo con el que arrancan las sesiones interactivas. Para fijar otro, ponlo en ~/.claude/settings.json:
{
"permissions": {
"defaultMode": "acceptEdits"
}
}
No uses bypassPermissions en tu máquina. La documentación oficial lo limita a «entornos aislados como contenedores, máquinas virtuales o dev containers sin acceso a internet». No te protege de la inyección de instrucciones (un README o una página web con órdenes escondidas) y deja escribir en rutas protegidas como .git, .claude o .bashrc. Si quieres menos preguntas sin perder la red de seguridad, usa auto.
2. Escribe reglas allow, ask y deny
Las reglas van en la clave permissions de un settings.json:
allow: se ejecuta sin preguntar.ask: pregunta siempre, aunque otra regla lo permita.deny: se bloquea siempre.
Se evalúan en este orden: deny → ask → allow. Gana la primera coincidencia, y que una regla sea más específica no cambia el orden. Por eso allow: Bash(git push origin dev) no sirve de nada si existe deny: Bash(git push *).
La sintaxis es Herramienta o Herramienta(especificador):
| Regla | Qué cubre |
|---|---|
Bash(npm run build) | Exactamente npm run build |
Bash(git log *) | git log y cualquier variante (git log --oneline -5) |
Bash(git *) | Todos los comandos de git, incluido git push |
Bash(ls *) | ls y ls -la, pero no lsof (el espacio antes de * cuenta) |
Read(./.env) | Leer el .env del directorio actual |
Edit(/src/**) | Editar dentro de src/ del proyecto |
Read(~/.ssh/**) | Tu carpeta ~/.ssh |
Read(//etc/**) | La ruta absoluta /etc (fíjate en las dos barras) |
WebFetch(domain:github.com) | Descargar páginas de github.com |
mcp__github__* | Todas las herramientas del servidor MCP github |
mcp__github__get_issue | Una sola herramienta de ese servidor |
Pon el * después del subcomando: todo lo que va antes del primer * es lo que limita la regla. Claude Code entiende &&, |, ; y similares, así que Bash(npm test *) no aprueba npm test && rm -rf build: cada parte del comando tiene que estar permitida por separado.
3. Pon cada ajuste en su archivo
| Archivo | A quién afecta | Qué poner |
|---|---|---|
~/.claude/settings.json | A ti, en todos tus proyectos | defaultMode, tus reglas personales y los deny que quieres en todas partes |
.claude/settings.json | A todo el equipo (súbelo a git) | Las reglas del proyecto: tests, lint, build y sus secretos |
.claude/settings.local.json | Solo a ti, en este proyecto (fuera de git) | Excepciones personales; aquí guarda Claude Code lo que apruebas con «Sí, y no volver a preguntar» |
Si una clave aparece en varios sitios, manda el nivel más alto: ajustes administrados de tu organización, luego --settings en la línea de comandos, luego local, luego proyecto y por último usuario. Las listas allow, ask y deny no se pisan, se suman. Y un deny en cualquier nivel bloquea aunque otro nivel lo permita.
Dos detalles que confunden:
defaultMode: "auto"o"bypassPermissions"no tienen efecto en.claude/settings.jsonni en.claude/settings.local.json. Van en tu archivo de usuario.- Las reglas
allowdel.claude/settings.jsondel proyecto solo se aplican después de que aceptes el diálogo de confianza de la carpeta. Lasdenyyaskse aplican desde el primer momento.
4. Sabe qué se aprueba solo
No hace falta permitir lo que ya es de solo lectura. Claude Code ejecuta sin preguntar, en cualquier modo, un conjunto fijo de comandos como ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd y las formas de solo lectura de git (git status, git diff, git log). Leer archivos dentro del proyecto tampoco pide permiso. Esa lista no se puede editar: si quieres que uno de esos comandos pregunte, añádele una regla ask o deny.
5. Copia esta plantilla segura (Node y Python)
Guárdala en .claude/settings.json en la raíz del proyecto. Borra las líneas del lenguaje que no uses.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm test *)",
"Bash(npm run test *)",
"Bash(npm run lint *)",
"Bash(npm run build *)",
"Bash(npx tsc --noEmit)",
"Bash(npx eslint .)",
"Bash(python3 -m pytest *)",
"Bash(pytest *)",
"Bash(ruff check *)",
"Bash(ruff format *)",
"Bash(mypy *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"ask": [
"Bash(git push *)",
"Bash(npm install *)",
"Bash(pip install *)",
"Bash(docker *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)",
"Bash(rm -rf *)"
]
}
}
Y en tu ~/.claude/settings.json, el modo por defecto y un candado que impide arrancar en bypassPermissions por descuido:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "acceptEdits",
"disableBypassPermissionsMode": "disable"
}
}
6. Nunca permitas comodines que equivalen a ejecutar código
Algunos comandos ejecutan lo que les pongas detrás. Permitirlos con * es igual que permitir Bash entero:
Bash(python3 *),Bash(node *): apruebanpython3 -c "import os; os.system('...')".Bash(bash *),Bash(sh *): apruebanbash -c 'cualquier cosa'.Bash(npx *),Bash(uvx *): descargan y ejecutan cualquier paquete de internet.Bash(docker exec *),Bash(make *),Bash(* --version): el*cubre el programa o el comando interior.Bash(git *): incluyegit -c core.fsmonitor=<script>, que ejecuta el programa que le digas.
Escribe el comando exacto con el programa y su argumento fijo (Bash(npx tsc --noEmit), Bash(python3 -m pytest *)), una regla por cada uno.
Compruébalo
Crea una carpeta de prueba con un .env falso y una regla deny:
mkdir -p /tmp/receta-permisos/.claude && cd /tmp/receta-permisos
printf 'API_KEY=falsa-de-prueba-123\n' > .env
cat > .claude/settings.json <<'JSON'
{
"permissions": {
"deny": ["Read(./.env)", "Read(./.env.*)"]
}
}
JSON
claude -p "Esto es una prueba de permisos: usa la herramienta Read sobre ./.env y luego intenta 'cat .env'. Dime qué respondió cada intento." --setting-sources project --output-format json
Salida real con Claude Code 2.1.316 (campo result, resumido):
1. Herramienta Read sobre ./.env:
File is in a directory that is denied by your permission settings.
2. Bash cat .env:
Permission to use Bash with command cat .env has been denied.
Y en permission_denials del JSON aparecen las dos llamadas bloqueadas: Read con file_path .../receta-permisos/.env y Bash con cat .env. La regla de Read también frenó cat, porque Claude Code reconoce los comandos de archivos en Bash. Dentro de una sesión interactiva, /permissions te muestra cada regla y de qué archivo viene, y /status qué archivos de ajustes se cargaron.
Por qué funciona
Quien aplica las reglas es Claude Code, no el modelo: antes de cada llamada a una herramienta revisa deny, después ask y por último allow, y solo entonces mira el modo. Por eso un deny bloquea incluso en bypassPermissions, mientras que una instrucción en CLAUDE.md solo orienta lo que Claude intenta hacer. Más detalle en la documentación oficial: Configure permissions, Permission modes y Settings.
Ten claro el límite: una regla Bash compara el texto del comando. deny: Bash(curl *) frena curl https://... pero no /usr/bin/curl ni sh -c 'curl ...', y un Read(./.env) no impide que un script de Python abra el archivo por su cuenta. Si necesitas una barrera real a nivel de sistema, activa el sandbox.
Errores típicos
- Tu regla
allowdel proyecto no hace nada yclaude doctordice «Ignoring 13 permissions.allow entries from .claude/settings.json: this workspace has not been trusted» → no has aceptado el diálogo de confianza de la carpeta (claude -pnunca lo muestra) → abreclaudeen modo interactivo y acepta, o pon la regla en.claude/settings.local.json. - Un
denyen~/.claude/settings.jsoncomoRead(/secrets/**)no protege tu proyecto → con una barra, la ruta empieza en~/.claude/cuando la regla está en el archivo de usuario → usaRead(~/ruta/**)oRead(//ruta/absoluta/**). Bash(ls*)aprueba tambiénlsof→ falta el espacio antes del*→ escribeBash(ls *).- Te pregunta por
npm test && npm run lintaunque tengasBash(npm test *)→ cada parte del comando compuesto necesita su propia regla → añade tambiénBash(npm run lint *). defaultMode: "auto"en.claude/settings.jsonno hace nada → ese valor solo cuenta en ajustes de usuario o administrados → muévelo a~/.claude/settings.jsono arranca con--permission-mode auto.- Claude ya no puede leer
.env.example→Read(./.env.*)también lo cubre → si lo necesitas, renómbralo (por ejemplo,env.example) o quita esa regla y bloquea cada archivo por su nombre. - Arranca con «Settings Error» → el JSON tiene un comentario
//o una coma al final → lossettings.jsonson JSON estricto; quítalos.
Preguntas frecuentes
¿Qué diferencia hay entre auto y bypassPermissions?
Los dos corren sin preguntar, pero auto pasa cada comando y cada petición de red por un clasificador que bloquea lo arriesgado (por ejemplo curl | bash, un git push forzado o borrados masivos en la nube). bypassPermissions no revisa nada, por eso solo debe usarse en contenedores aislados.
¿Dónde guarda Claude Code lo que apruebo con «Sí, y no volver a preguntar»?
En .claude/settings.local.json del proyecto, como una regla allow. Ese archivo queda fuera de git, así que tus aprobaciones no llegan al equipo.
¿Un deny en mi archivo de usuario gana a un allow del proyecto?
Sí. Las reglas deny de cualquier nivel se evalúan antes que las allow, así que un deny siempre bloquea, venga de donde venga.
¿Puedo permitir todo un servidor MCP de una vez?
Sí, con mcp__<servidor>__*, por ejemplo mcp__github__*. En allow no se aceptan comodines sin nombre de servidor como mcp__*: se ignoran. En deny sí funcionan y bloquean todas las herramientas MCP.