Módulo 3 — El harness: diseñar el entorno donde trabaja el agente
4. Herramientas y permisos: mínimo privilegio para el agente
Descripción
Al terminar esta lección vas a poder configurar, en tu propio repositorio, una política de permisos de mínimo privilegio: qué puede hacer el agente sin pedirte nada, qué necesita tu confirmación explícita cada vez, y qué queda bloqueado sin importar lo que diga el prompt del momento — usando las reglas reales de allow, ask y deny que tu herramienta ya soporta, no una promesa de buen comportamiento escrita en prosa.
Esto importa en el trabajo real porque la pregunta no es teórica: cualquier equipo que le da a un agente acceso de shell sin una política explícita está, en los hechos, decidiendo por omisión que el agente puede intentar cualquier comando que se le ocurra — incluido uno que borre una rama compartida, aplique una migración contra la base de datos equivocada, o filtre una credencial que estaba a la vista en un .env. No hace falta que el agente actúe con mala intención: basta con que malinterprete la tarea, o que un archivo que lee contenga una instrucción inyectada, para que el resultado sea el mismo.
Conexión con el módulo: en la lección anterior viste cómo escribir un archivo de instrucciones que el agente respeta de verdad. Esta lección toma la otra mitad del problema: qué pasa cuando, por la razón que sea, el agente no respeta esa regla. Las instrucciones moldean lo que el agente intenta hacer; los permisos deciden, a nivel del propio harness, qué le está permitido ejecutar, sin importar qué intente — son dos sistemas distintos, y confundirlos es el primer error común de esta lección. La lección siguiente completa el bucle: una vez que sabes qué puede tocar el agente, falta que sepa si lo que tocó funcionó.
El sistema de tarjetas: qué abre solo, qué pide una firma y qué no tiene puerta
Piensa en el primer día de un empleado nuevo en un almacén grande. Le entregan una tarjeta de acceso, y esa tarjeta ya viene programada con un criterio, no con una lista interminable de puertas prohibidas. Abre sola la puerta principal, el área de empaque, el depósito de herramientas comunes — el empleado nunca tiene que pedir permiso para entrar ahí, y nadie del turno anterior tiene que aprobarlo cada vez. Para el cuarto de mantenimiento eléctrico, la tarjeta no abre por sí sola: activa un aviso al supervisor, que tiene que acercarse y autorizar esa entrada puntual, cada vez, sin excepción. Y para la bóveda de inventario de alto valor, no existe siquiera un lector de tarjeta en esa puerta — no es que el empleado tenga baja prioridad ahí, es que físicamente no hay forma de entrar con lo que le dieron el primer día, sin importar cuán urgente suene el pedido.
Un agente de código funciona con la misma lógica de tres niveles, y la palabra que usa la industria para cada nivel es casi literal: reglas de allow (la puerta que abre sola), ask (la puerta que siempre llama al supervisor) y deny (la puerta sin lector, la que ninguna instrucción del momento puede abrir). Hay un detalle de la tarjeta del almacén que se traduce exacto a cómo funciona esto en la práctica: si una puerta no tiene lector, no importa que el empleado tenga además una autorización especial y muy específica para "cualquier bodega del edificio" — esa autorización no vence a la ausencia física del lector. Claude Code documenta esta misma jerarquía sin ambigüedad: las reglas se evalúan en un orden fijo —primero deny, luego ask, luego allow—, y la primera que hace match decide el resultado, sin importar qué tan específica sea una regla que compita en otro nivel. Una regla amplia como Bash(git push *) en deny bloquea cualquier variante de ese comando, aunque exista en otro lugar una regla más puntual en allow para un caso concreto de push — la especificidad no cambia el orden de evaluación.
En Claude Code, esta política vive en un archivo de configuración —normalmente .claude/settings.json en la raíz del repositorio, para que todo el equipo comparta la misma tarjeta— con tres arreglos: allow, ask y deny. Cada regla tiene la forma Herramienta(patrón): Bash(pytest *) para un prefijo de comando de shell, Read(./.env) para una ruta de archivo, WebFetch(domain:example.com) para un dominio. Otras herramientas —Cursor, Codex CLI, y el resto— resuelven el mismo problema con un mecanismo equivalente bajo otro nombre; lo que sigue usa la sintaxis de Claude Code porque es la que se puede verificar línea por línea contra su documentación, pero el criterio de diseño —qué corre solo, qué pide confirmación, qué queda bloqueado— se transfiere sin cambios a cualquier herramienta que uses.
Ejemplo trabajado: el mismo repositorio, dos políticas de permisos
Retoma el proyecto FastAPI de la lección 2 —ese donde agregaste el endpoint GET /api/profile—. El equipo quiere que el agente siga iterando sobre ese repositorio sin que alguien tenga que aprobar cada comando, pero tampoco quiere darle vía libre a cualquier cosa. Empieza por ver qué pasa sin ninguna política escrita.
ls -la .claude/settings.json 2>/dev/null
Qué esperar (sin política): el comando no imprime nada — el archivo no existe. Con eso, el modo de permisos por defecto (default) queda activo: cada comando de shell nuevo que el agente intenta —pytest, ruff check, git commit— dispara una pregunta la primera vez que aparece en la sesión, sin distinguir uno de bajo riesgo de uno de alto riesgo. La única excepción es un conjunto fijo de comandos de lectura pura que Claude Code reconoce en cualquier modo —entre ellos ls, cat, grep, y las formas de solo lectura de git, como git status o git log— que corren sin preguntar aunque no exista ningún archivo de configuración.
Dale ahora al agente esta tarea: "Agrega un endpoint DELETE /api/profile que borre la cuenta del usuario autenticado. Corre las pruebas y haz commit del cambio." Sin política, cada paso que no sea de esa lectura pura —correr pytest, correr ruff, hacer el commit— dispara una aprobación individual. Y no hay ninguna barrera que distinga "correr las pruebas del proyecto" de "aplicar una migración contra la base de datos equivocada": las dos piden exactamente lo mismo, tu aprobación puntual, en el momento, bajo la presión de seguir avanzando.
Ahora el equipo escribe la política, versionada en el repositorio, en .claude/settings.json:
{
"permissions": {
"allow": [
"Bash(pytest *)",
"Bash(ruff check *)",
"Bash(mypy *)",
"Bash(git commit *)"
],
"ask": [
"Bash(git push *)",
"Bash(alembic upgrade *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(git branch -D*)",
"Bash(terraform apply*)",
"Bash(terraform destroy*)"
]
}
}
Con este archivo en la raíz del repositorio, corre la misma tarea otra vez.
Qué esperar (con política): el agente corre pytest, ruff check y mypy sin ninguna interrupción — están en allow. Cuando llega al commit, tampoco pregunta: Bash(git commit *) también está permitido. Si en algún punto decidiera correr alembic upgrade head —por ejemplo, porque interpretó que borrar una cuenta necesitaba un cambio de esquema—, ahí sí se detiene y te pregunta, porque una regla de ask siempre pide confirmación, sin importar qué tan de rutina parezca esa vez. Y si por cualquier motivo —una instrucción mal interpretada, contenido inyectado en un archivo que leyó— el agente intentara leer .env o forzar un push, la respuesta ni siquiera te llega como pregunta: la herramienta está bloqueada antes de que la sesión te lo muestre.
La diferencia no es que el segundo repositorio tenga un agente "más disciplinado". Es que en el primero, cada decisión de qué es seguro correr la tomas tú, en vivo, comando por comando; en el segundo, la tomaste una vez, por escrito, y la aplica el mismo harness sin depender de que el modelo la recuerde o la respete.
Zonas prohibidas típicas: lo que ningún agente debería poder tocar sin ti
Cinco zonas aparecen una y otra vez, en cualquier repositorio con algo de producción detrás, como candidatas obligadas a deny —no a ask, porque no hay contexto donde valga la pena preguntar dos veces antes de decir que no:
| Zona | Por qué es candidata a deny, no a ask | Regla de ejemplo |
|---|---|---|
| Credenciales | Una vez que un secreto entra al contexto de la conversación, no hay forma confiable de "olvidarlo" — y preguntar tampoco sirve, porque para cuando llega la pregunta el archivo ya se leyó. | Read(./.env), Read(./.env.*), Read(./secrets/**), Read(~/.ssh/**) |
| Historia y ramas de git | Reescribir el pasado (push --force, reset --hard) o borrar una rama afecta a cualquier otra persona o sesión que trabaje sobre el mismo repositorio en paralelo, no solo al agente que lo ejecutó. | Bash(git push --force*), Bash(git reset --hard*), Bash(git branch -D*) |
| Infraestructura | terraform apply o kubectl delete no modifican un archivo: modifican un sistema en producción que sigue corriendo después de que la sesión del agente terminó. | Bash(terraform apply*), Bash(terraform destroy*), Bash(kubectl delete*) |
| Migraciones contra producción | Una migración de esquema mal escrita no se revierte con un git checkout: puede perder datos reales de usuarios reales. | Depende de contra qué base apunta el comando — la sección de hooks más adelante explica por qué un prefijo de Bash no alcanza para distinguir esto. |
| Base de datos de producción | Cualquier acceso directo de lectura o escritura a producción, por fuera del código de la aplicación que ya pasó por revisión. | Bloquear el servidor MCP o la herramienta completa que conecta con producción; la separación real de entornos se profundiza en la lección 6 de este módulo. |
En equipos donde varias personas —o varios agentes— trabajan sobre el mismo árbol de trabajo al mismo tiempo, la fila de "historia y ramas" suele ir todavía más lejos: no solo se bloquea reescribir el pasado, se bloquea también que el agente cree ramas nuevas sin que alguien lo pida de forma explícita (Bash(git checkout -b*), Bash(git switch -c*)), porque un cambio de rama hecho por una sesión cambia lo que ve cualquier otra sesión que esté trabajando en paralelo sobre ese mismo directorio.
Mínimo privilegio en la práctica: por qué la lista de permitidos vence a la de prohibidos
La tabla anterior es corta a propósito, y conviene no malinterpretarla: no es un ejercicio de "enumera todo lo peligroso que se te ocurra" — es una lista curada de zonas que nunca tienen un caso de uso legítimo dentro de una sesión de agente. Para todo lo demás, la estrategia general no es simétrica entre "permitir y bloquear lo peligroso" versus "bloquear todo y permitir lo verificado", y vale la pena entender por qué antes de escribir tu propia política desde cero.
Imagina que, en vez de la política del ejemplo anterior, alguien intenta resolver el mismo problema al revés: dejar todo abierto por defecto y bloquear puntualmente los comandos que se le ocurren como peligrosos. Para restringir que el agente solo descargue código de un repositorio de confianza, escribe esta regla:
{ "permissions": { "allow": ["Bash(curl http://github.com/*)"] } }
La intención es clara: que curl solo llegue a GitHub. Pero un patrón de shell que intenta acotar argumentos así es frágil por construcción, y no hace falta ni el intento de esquivarlo — variantes normales del mismo comando ya se le escapan: pasar una opción antes de la URL (curl -X GET http://github.com/...), usar https:// en vez de http://, apoyarse en una redirección desde un dominio que sí matchea pero reenvía a otro lado, o simplemente guardar la URL en una variable de entorno antes de invocar curl. Cada una de esas variantes es un caso más que hay que anticipar, y la lista de variantes no tiene techo — siempre hay una combinación de flags, protocolo o indirección que nadie escribió todavía.
Esa es la asimetría. Enumerar todo lo peligroso que existe es una lista sin fin, porque lo peligroso se reinventa con cada flag nuevo de cada herramienta. Enumerar lo que tu equipo de verdad necesita correr a diario —pytest, ruff check, git commit— es una lista corta, conocida, y verificable en un vistazo. Por eso el diseño que funciona no es "todo abierto, bloqueo lo que reconozco como peligroso": es "todo cerrado por defecto —cualquier cosa que no esté en la lista pide confirmación—, y voy agregando a la lista de permitidos solo lo que ya verifiqué que es seguro en este repositorio." La lista de prohibidos sigue teniendo un lugar —las zonas de la tabla anterior—, pero como último cerrojo sobre casos ya conocidos, no como la defensa principal.
Cuando el prefijo no alcanza: hooks para las zonas que allow/deny no distinguen solas
La fila de "migraciones" de la tabla quedó pendiente a propósito. Un prefijo de Bash no puede distinguir "correr alembic upgrade head contra la base de datos de prueba local" de "correr exactamente el mismo comando contra la base de datos de producción" — el texto del comando es idéntico en los dos casos; lo que cambia es una variable de entorno o un archivo de configuración que el prefijo no ve.
Para este tipo de caso, Claude Code ofrece un mecanismo aparte: un hook PreToolUse, un script que tú mismo escribes y que corre antes de que se muestre cualquier prompt de aprobación. El hook puede inspeccionar la llamada completa —no solo el prefijo del comando— y decidir si la bloquea, si fuerza una pregunta, o si la deja pasar. La propia documentación lo recomienda exactamente para este tipo de caso: para el ejemplo del curl de la sección anterior, la alternativa robusta que ofrece no es "escribe un prefijo más largo", es "usa un hook que valide la URL real antes de dejar correr el comando".
Aplicado a las migraciones: un hook puede leer la variable DATABASE_URL (o el argumento de conexión de tu herramienta de migraciones) antes de dejar pasar el comando, y bloquear cualquier llamada cuya cadena de conexión no apunte a un host que reconozcas como entorno de prueba. Eso es exactamente lo que una regla de allow/ask/deny sobre el texto del comando no puede hacer por sí sola, porque el texto no cambia entre los dos casos — la decisión depende de un dato que solo se conoce en el momento de ejecutar.
Esta es la única herramienta de esta lección que exige escribir código propio, y por eso queda como profundización: la mayoría de las zonas prohibidas de la tabla anterior se resuelven con reglas simples de allow/ask/deny; solo cuando el mismo texto de comando puede ser seguro o catastrófico según un dato externo, hace falta un hook.
El costo real de aprobar todo para no ser interrumpido
Escribir una política de mínimo privilegio, con sus tres listas, toma tiempo. Aprobar todo y no volver a pensarlo toma un segundo. La tentación de la segunda opción es real, y las herramientas suelen ofrecer un modo que hace exactamente eso — en Claude Code se llama bypassPermissions, y salta la aprobación de básicamente cualquier acción, con dos excepciones puntuales: reglas de ask escritas a mano, y un intento de borrar la raíz del sistema de archivos o tu directorio home completo (rm -rf /, rm -rf ~), que sigue disparando una pregunta como último cerrojo contra un error grosero del modelo.
Todo lo demás en ese modo corre sin que nadie lo revise — incluidas escrituras dentro de .git, .claude, .vscode y otras carpetas de configuración de herramientas que ese modo deja explícitamente sin proteger. La propia documentación es explícita sobre dónde es aceptable ese riesgo: úsalo solo en un entorno aislado —un contenedor o una máquina virtual— donde, si algo sale mal, no hay nada de valor real que perder. Activarlo como el modo por defecto de la laptop de cada desarrollador del equipo es otra cosa: ahí sí hay credenciales, hay una copia del repositorio con historia real, y no hay ningún aislamiento entre lo que el agente puede tocar y lo que de verdad importa.
Ya viste en la lección 1 de este módulo que delimitar de antemano qué puede tocar el agente —en vez de preguntar acción por acción— redujo en 84% las interrupciones por permisos en el uso interno de Anthropic, sin bajar la seguridad. Ese dato importa de nuevo aquí porque muestra que la fricción y la seguridad no están necesariamente enfrentadas: esa reducción no vino de apagar las verificaciones, vino de invertir el tiempo en escribir, una sola vez, una lista de permitidos que refleja lo que el equipo de verdad usa todos los días. Esa lista es exactamente lo que armaste en el ejemplo trabajado de esta lección — la diferencia entre "no me interrumpas nunca" y "no me interrumpas por lo que ya verifiqué que es seguro" es la que separa un repositorio con mínimo privilegio real de uno que solo apagó la luz de alarma.
Errores comunes
Confundir una regla escrita en el archivo de instrucciones con una restricción de permisos real (conceptual). Qué pasa: el equipo escribe en CLAUDE.md algo como "nunca toques la base de datos de producción" y da el problema por resuelto, sin agregar ninguna regla equivalente en settings.json. Por qué pasa: la frase suena como una regla, y en la mayoría de las sesiones el agente de hecho la respeta —eso es exactamente lo que viste en la lección anterior—, así que es fácil olvidar que instrucciones y permisos son dos sistemas distintos. La documentación de Claude Code lo dice sin rodeos: "permission rules are enforced by Claude Code, not by the model" — las instrucciones moldean lo que el agente intenta, los permisos deciden qué el harness permite, y solo el segundo sistema es una barrera real. Cómo detectarlo: por cada "nunca" que tengas escrito en tu archivo de instrucciones sobre credenciales, producción o infraestructura, busca si existe la regla de deny equivalente en settings.json — si solo existe como prosa, no hay ninguna barrera enforced, hay una expectativa. Cómo corregirlo: toda regla de "nunca" sobre una zona sensible necesita su contraparte en permisos; la instrucción explica el porqué, la regla garantiza el resultado incluso si el agente malinterpreta o ignora la prosa.
Tratar una denylist de comandos como si diera la misma protección que una allowlist (conceptual). Qué pasa: el equipo escribe un puñado de reglas de deny intentando nombrar cada comando peligroso que se les ocurre —rm -rf, un patrón para DROP TABLE, terraform destroy— y da el repositorio por protegido. Por qué pasa: enumerar peligros conocidos se siente concreto y completo, pero un patrón de bloqueo sobre argumentos de shell es frágil por diseño —siempre hay una variante no contemplada: otro flag, una variable de entorno, un alias, un script intermedio—, como viste con el ejemplo del curl restringido a GitHub. Cómo detectarlo: pregúntate cuántas formas distintas existen de lograr el mismo efecto que tu regla intenta bloquear; si se te ocurre una segunda en menos de un minuto, tu denylist tiene un hueco. Cómo corregirlo: invierte la lógica — define primero qué sí puede hacer el agente sin preguntar (una lista corta y verificada), deja todo lo demás en modo preguntar por defecto, y reserva el deny explícito solo para las zonas que jamás deberían ejecutarse pase lo que pase.
Activar un modo que aprueba todo para no seguir siendo interrumpido, sin acotarlo a un entorno aislado (práctico). Qué pasa: alguien configura bypassPermissions (o el equivalente en su herramienta) como modo por defecto en la laptop de trabajo de cada desarrollador, no en un contenedor descartable. Por qué pasa: la fricción de aprobar comando por comando es real y cansa rápido, y ese modo parece la salida más simple. Cómo detectarlo: revisa dónde está configurado ese modo — si es el defaultMode de un .claude/settings.json compartido que corre sobre repositorios con credenciales e historia real, ya caíste en esto. Cómo corregirlo: la fricción real se resuelve con una allowlist curada de lo que el equipo usa a diario, como en el ejemplo trabajado de esta lección — no con un interruptor que apaga todas las verificaciones a la vez.
Ejercicios
Ejercicio 1 — Clasifica y escribe la regla. Tienes el mismo repositorio FastAPI de la lección 2, con alembic para migraciones, y esta lista de operaciones que el agente podría necesitar en una sesión típica. Para cada una, decide si corresponde a allow, ask o deny, y escribe la regla exacta:
- Correr la suite completa con
pytest. - Ejecutar
git commit -m "..."sobre cambios ya revisados por el agente. - Ejecutar
git push origin feature/delete-account. - Leer el archivo
.envde la raíz del proyecto. - Ejecutar
git reset --hard HEAD~3. - Ejecutar
terraform applysobre la infraestructura del proyecto.
Ver solución
allow—Bash(pytest *). Es la señal de retorno que el equipo quiere que el agente use todo el tiempo sin fricción; no hay riesgo real en correr la suite de pruebas.allow—Bash(git commit *). Un commit local es reversible —git resetlo deshace sin tocar el remoto— y es exactamente el tipo de operación de rutina que una allowlist bien pensada debería dejar pasar.ask—Bash(git push *). Empujar al remoto compartido sí tiene un costo si sale mal —lo ven otras personas y otras sesiones—, pero no es tan grave como para bloquearlo siempre; una confirmación puntual es proporcional al riesgo.deny—Read(./.env). Una credencial no se "pregunta primero": una vez que entra al contexto de la conversación ya está expuesta. No hay escenario donde valga la pena preguntar en vez de bloquear.deny—Bash(git reset --hard*). Reescribe la historia local de forma irreversible y afecta a cualquiera que comparta ese árbol de trabajo; no es una operación de rutina que amerite solo una pregunta.deny—Bash(terraform apply*). Modifica infraestructura real que sigue viva después de que termine la sesión; el radio de impacto excede por completo lo que una sesión de código debería poder decidir sola.
Por qué funciona esta clasificación: el criterio que separa ask de deny no es "qué tan mal se ve el comando" sino si existe algún escenario legítimo donde preguntar una vez sea suficiente (push) frente a uno donde ningún contexto justifica siquiera intentarlo (reset --hard, terraform apply, leer un secreto).
Ejercicio 2 — La denylist que no protege lo que cree proteger. Alguien en tu equipo escribe esta regla, convencido de que ya bloqueó el acceso a las credenciales del proyecto:
{ "permissions": { "deny": ["Bash(cat ./secrets/*.json)"] } }
¿Qué dos formas tiene el agente de leer el contenido de esos archivos sin que esta regla lo detecte? ¿Cuál es la regla que sí cierra el problema, y qué límite le queda incluso a esa regla mejor escrita?
Ver solución
Dos formas concretas: (1) usar cualquier otro comando de lectura sobre esa misma ruta —head ./secrets/api-key.json, tail, incluso cat escrito de otra forma— porque la regla hace match contra ese patrón puntual, no contra "cualquier forma de leer ese directorio"; (2) escribir un script propio —dos líneas de Python que hacen open("secrets/api-key.json").read(), corridas con Bash(python *)— porque eso ya no es un comando de shell reconocido como lectura de archivo, es un proceso arbitrario que abre el archivo por su cuenta.
La regla que sí cierra el primer caso es Read(./secrets/**) como deny: Claude Code aplica las reglas de Read/Edit no solo a sus propias herramientas de lectura, sino también a los comandos de Bash que reconoce como lectura de archivo —cat, head, tail, sed—, así que un head o un cat con otra ruta dentro de esa carpeta también quedan bloqueados por la misma regla.
El límite que le queda incluso a esa regla mejor escrita es el segundo caso: un script que el agente escribe y ejecuta él mismo abre el archivo por su cuenta, no a través de un comando que Claude Code reconozca como lectura de archivo, así que la regla de Read no lo detecta. Cerrar esa brecha por completo —para cualquier proceso arbitrario, no solo los comandos reconocidos— es trabajo de una capa de aislamiento a nivel de sistema operativo, que queda fuera del alcance de esta lección.
Por qué funciona esta respuesta: distingue exactamente el error conceptual de la sección de allowlist-versus-denylist —una regla que enumera un caso puntual (cat sobre un patrón de nombre) siempre deja huecos que una regla que actúa sobre el recurso protegido (Read sobre la ruta) no deja, al menos para los caminos que la herramienta reconoce.
Ejercicio 3 — El interruptor que apaga todo. Un equipo, cansado de aprobar comando por comando, agrega esto al .claude/settings.json que está comiteado en el repositorio y que usan todos en sus laptops de trabajo:
{ "permissions": { "defaultMode": "bypassPermissions" } }
Explica qué es exactamente lo que dejó de estar protegido con este cambio, y qué harías en su lugar para lograr la misma reducción de fricción sin el mismo riesgo.
Ver solución
Con bypassPermissions como modo por defecto, prácticamente todo corre sin preguntar —incluidas escrituras dentro de .git, .claude, .vscode y otras carpetas de configuración que ese modo deja explícitamente sin proteger—, con solo dos excepciones puntuales: reglas de ask escritas a mano, y un intento de borrar la raíz del sistema de archivos o el directorio home completo. Ninguna credencial, ninguna migración, ningún push forzado queda cubierto por esas dos excepciones. Y el problema no es solo técnico: el equipo lo configuró en el archivo compartido que corre en la laptop de cada desarrollador, no en un contenedor aislado sin nada de valor real adentro, que es el único contexto donde la propia documentación recomienda usar este modo.
En su lugar, la reducción de fricción real se logra con lo que ya viste en esta lección: una lista de allow curada con los comandos que el equipo de verdad corre todos los días (pruebas, lint, tipos, commits locales), dejando el resto en modo de pregunta por defecto, y una lista corta de deny para las zonas que jamás deberían ejecutarse pase lo que pase. Esa combinación logró, en el uso interno de Anthropic, una reducción de interrupciones comparable a la que busca este equipo, sin apagar ninguna verificación real.
Por qué funciona esta respuesta: aplica el argumento completo de "el costo real de aprobar todo" a un caso concreto —la fricción y la seguridad no están enfrentadas, lo que está enfrentado es la pereza de no escribir la política y el riesgo de no tener ninguna.
Resumen y siguiente paso
Ya puedes escribir, para un repositorio real, una política de permisos de mínimo privilegio: qué corre sin preguntar, qué pide confirmación siempre, y qué queda bloqueado sin importar el prompt del momento, usando reglas de allow, ask y deny reales, no una expectativa escrita en prosa. También puedes nombrar las zonas que casi siempre merecen deny —credenciales, historia y ramas de git, infraestructura, migraciones y bases de datos de producción— y explicar por qué una lista de lo permitido resiste mejor que una lista de lo prohibido, incluso cuando las dos parecen cubrir los mismos casos sobre el papel.
Todo lo que hiciste en esta lección responde una sola pregunta: qué puede tocar el agente. No responde la pregunta que sigue, que es si lo que tocó de verdad funcionó. Un agente con la política de permisos perfecta puede correr pytest sin ninguna interrupción y aun así entregarte un cambio roto, si nadie configuró una señal que le diga, en el momento, que algo falló.
Antes de avanzar a la lección 5 deberías poder: escribir de memoria la estructura mínima de un .claude/settings.json con las tres listas; explicar con tus propias palabras por qué deny le gana a allow sin importar qué tan específica sea la regla que compite; y nombrar al menos tres zonas de tu propio repositorio de trabajo que hoy no tienen ninguna regla de deny y deberían tenerla.
La siguiente lección toma esa pregunta pendiente —¿funcionó de verdad?— y construye el bucle de retroalimentación completo: pruebas, tipos y linter como las tres fuentes de señal más baratas, y por qué la duración de ese bucle importa más que su exhaustividad.
Recursos
- Configure permissions — Claude Code — referencia completa de reglas
allow/ask/deny, sintaxis deBash,Read,Edit,WebFetchymcp__, el orden de evaluación deny-ask-allow, y por qué un patrón de argumentos sobrecurles frágil. - Settings — Claude Code — estructura y ubicación de
.claude/settings.json, y cómo distribuir la misma política de permisos a todo el equipo desde el repositorio. - Permission modes — Claude Code — cuándo usar cada modo (
default,acceptEdits,plan,bypassPermissions) y los riesgos concretos de saltarse las aprobaciones. - Automate actions with hooks — Claude Code — cómo escribir un hook
PreToolUsepara los casos donde un prefijo de comando no alcanza, como distinguir una migración de prueba de una contra producción. - Beyond permission prompts: making Claude Code more secure and autonomous — Anthropic — la fuente del dato de 84% de reducción de interrupciones citado en esta lección y en la lección 1 del módulo.