Cap 28 — Auditoría de Configuración de Agentes
§ 0 — Cómo usar este capítulo
Este capítulo tiene dos públicos declarados:
- Humano: es un checklist comentado. Cada ficha explica un antipatrón real de configuración de Claude Code, por qué duele en operación y cómo se corrige, con el hecho canónico y su URL.
- Agente: es un protocolo imperativo. Un agente de Claude Code puede leer este archivo y ejecutar la auditoría completa sobre un repositorio sin consultar nada más.
0.1 Prompt de arranque
Copia este prompt tal cual en una sesión de Claude Code abierta en el repositorio que quieres auditar:
Lee el capítulo 28 completo y ejecuta el PROTOCOLO DE AUDITORÍA (§1) sobre este repositorio. Recorre las 6 fases sin omitir ninguna y evalúa TODAS las fichas del catálogo (§3-§6). Trabaja en solo lectura: no modifiques ningún archivo. Entrega el informe con el formato de §8 y guárdalo en `AUDITORIA-CONFIG.md`.
0.2 Modo de ejecución recomendado
Ejecuta la auditoría en plan mode (Shift+Tab, --permission-mode plan, o prefijando el prompt con /plan) o en auto mode. En plan mode, con useAutoModeDuringPlan activo (default), el clasificador aprueba comandos de solo lectura sin preguntar y las ediciones quedan bloqueadas hasta aprobar el plan, que es exactamente la postura que necesita una auditoría (https://code.claude.com/docs/en/permission-modes).
Nunca uses bypassPermissions para auditar. La documentación lo reserva a entornos aislados: «Only use this mode in isolated environments like containers or VMs» (https://code.claude.com/docs/en/permissions). Además ese modo salta los prompts incluso para escrituras en .claude/, es decir, permite que el propio agente reescriba la configuración que está auditando.
0.3 Autocontención
El agente no debe leer los capítulos 1 a 27 ni consultar la web. Cada ficha del catálogo trae su hecho canónico completo y su URL de referencia. Si una ficha no alcanza para decidir, el veredicto es NO_VERIFICABLE, no una búsqueda externa.
0.4 Anclaje de versión
Muchas fichas describen comportamientos que empezaron en una versión concreta. Si claude --version devuelve una versión inferior a la indicada en el campo “Desde” de una ficha, esa ficha se marca NO_APLICA, nunca HALLAZGO. Si la versión instalada es superior o igual, la ficha se evalúa normalmente.
0.5 Comandos previos obligatorios
Antes de la fase 1, el agente ejecuta y registra la salida de:
claude --version # ancla de versión para todo el informe
claude doctor # instalación, auto-actualización, integridad
Y dentro de la sesión interactiva:
Si la sesión es headless y /context no está disponible, se registra NO_VERIFICABLE para las fichas que dependan de la carga efectiva de memoria.
§ 1 — El protocolo en seis fases
flowchart TD
F1[F1 · Inventario<br/>listar archivos de configuración] --> F2[F2 · Versión<br/>claude --version + claude doctor]
F2 --> F3[F3 · Evaluación<br/>10 familias del catálogo]
F3 --> F4[F4 · Clasificación<br/>severidad + capadores]
F4 --> F5[F5 · Informe<br/>JSON primero, Markdown después]
F5 --> F6{¿El usuario autoriza<br/>la remediación?}
F6 -->|No| FIN[Fin · entrega solo lectura]
F6 -->|Sí| F6B[F6 · Remediación<br/>diff por diff + verificación]
F6B --> FIN
F1 — Inventario
Listar y registrar la existencia (o ausencia) de cada ruta. La ausencia se registra explícitamente: no se infiere contenido de un archivo que no existe.
| Ruta | Qué es | Comando de listado |
|---|
/Library/Application Support/ClaudeCode/managed-settings.json (macOS) | Managed settings | ls -l "/Library/Application Support/ClaudeCode/" |
/etc/claude-code/managed-settings.json (Linux/WSL) | Managed settings | ls -l /etc/claude-code/ |
C:\Program Files\ClaudeCode\managed-settings.json (Windows) | Managed settings | dir "C:\Program Files\ClaudeCode" |
~/.claude/settings.json | Settings de usuario | ls -l ~/.claude/ |
~/.claude/CLAUDE.md | Memoria de usuario | wc -l ~/.claude/CLAUDE.md |
~/.claude/rules/*.md | Reglas de usuario | ls -l ~/.claude/rules/ |
~/.claude/skills/, ~/.claude/agents/ | Skills y subagentes de usuario | ls -R ~/.claude/skills ~/.claude/agents |
~/.claude.json | Servidores MCP de scope local y user | jq 'keys' ~/.claude.json |
~/.claude/projects/<project>/memory/MEMORY.md | Auto memory | wc -l ~/.claude/projects/*/memory/MEMORY.md |
./CLAUDE.md o ./.claude/CLAUDE.md | Memoria de proyecto | wc -l CLAUDE.md .claude/CLAUDE.md |
./CLAUDE.local.md | Memoria local: preferencias personales del proyecto, va en .gitignore. Scope vigente; en repos con varios worktrees la doc recomienda un import desde el home en su lugar | ls -l CLAUDE.local.md |
./AGENTS.md | Instrucciones de otros agentes | ls -l AGENTS.md |
.claude/settings.json | Settings de proyecto (versionado) | jq . .claude/settings.json |
.claude/settings.local.json | Settings locales; desde v2.1.211 vive en la raíz del repo git | jq 'keys' "$(git rev-parse --show-toplevel)/.claude/settings.local.json" |
.claude/rules/*.md | Reglas path-scoped | ls -l .claude/rules/ |
.claude/skills/*/SKILL.md | Skills de proyecto | ls .claude/skills/*/SKILL.md |
.claude/commands/*.md | Comandos (fusionados con skills) | ls .claude/commands/ |
.claude/agents/**/*.md | Subagentes | find .claude/agents -name '*.md' |
.mcp.json | Servidores MCP de scope project | jq . .mcp.json |
.claude-plugin/plugin.json, .claude-plugin/marketplace.json | Plugin o marketplace local | ls -l .claude-plugin/ |
Comando de barrido del inventario:
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
ls -la "$ROOT/CLAUDE.md" "$ROOT/AGENTS.md" "$ROOT/.mcp.json" 2>/dev/null
find "$ROOT/.claude" -maxdepth 2 -type f 2>/dev/null | sort
ls -la ~/.claude/ 2>/dev/null
F2 — Verificación de versión
Se ejecuta antes de evaluar cualquier ficha, porque el veredicto de alrededor de catorce fichas depende de la versión instalada (todas las que llevan campo “Desde”). Se registra la versión en el campo cli_version del informe y se pone catalogo_desfasado: true si la versión instalada es mayor que v2.1.217, señalando que pueden existir cambios no cubiertos por el catálogo.
F3 — Evaluación
Recorrer las diez familias del catálogo sin omitir ninguna:
MEM · PERM · SBX · SET · SKL · AGT · HOOK · MCP · MOD · CTX, más el catálogo de patrones P-* (§5).
F4 — Clasificación
Asignar severidad a cada hallazgo (§2) y evaluar los capadores del veredicto global (§7).
Emitir JSON primero, Markdown después (§8). El Markdown se deriva del JSON.
Solo con autorización explícita del usuario (§9). Si el usuario no la da, el trabajo termina en F5.
1.1 Reglas de ejecución — no negociables
- Fases F1-F5 en solo lectura. Ninguna escritura hasta F6, y solo tras aprobación explícita del usuario. Esto incluye el propio archivo
AUDITORIA-CONFIG.md: se escribe al final, como entrega, no durante el recorrido.
- Veredicto tipado por ficha:
HALLAZGO | LIMPIO | NO_APLICA | NO_VERIFICABLE. No existe un quinto valor.
- Prohibido
HALLAZGO sin evidencia. Se cita ruta absoluta + número de línea + texto literal, o bien el comando ejecutado y su salida. Sin eso el veredicto es NO_VERIFICABLE.
- Prohibido inventar fichas o IDs. El vocabulario es cerrado. Si el agente detecta algo que no está en el catálogo, va en
observaciones[] sin severidad y sin ID, nunca como hallazgo.
NO_APLICA obligatorio y explícito. Nunca se omite una ficha en silencio: toda ficha del catálogo aparece en el conteo final.
- Distinguir
ausente de no_aplica. Un archivo que no existe se registra como ausente; no se infiere su contenido ni se asume que la ficha está limpia.
- Redacción de secretos.
.claude/settings.local.json, ~/.claude.json y los headers de MCP pueden contener credenciales. Se reportan rutas y nombres de clave, nunca valores. Todo valor con forma de token, clave o password se emite como [REDACTADO].
- Antisesgo del revisor. La doc advierte: «a reviewer prompted to find gaps will usually report some, even when the work is sound… Chasing every finding leads to over-engineering» (https://code.claude.com/docs/en/best-practices). Solo se reporta lo que afecta corrección, seguridad o consumo de contexto. Nada hipotético, nada de “podría convenir revisar”.
- Autoverificación de cobertura. Antes de emitir el informe, el agente comprueba que
fichas_evaluadas == fichas_totales, que ningún HALLAZGO carece de evidencia y que ninguno carece de fuente. Si algún contador falla, repite la familia afectada antes de emitir.
- Presupuesto de contexto. Si el inventario supera aproximadamente 40 archivos de configuración, delegar el barrido de una familia completa a un subagente con instrucción explícita de devolver un resumen de 1.000-2.000 tokens, que es el rango recomendado para respuestas de subagente (https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).
§ 2 — Anatomía de una ficha
Cada ficha del catálogo tiene siete campos:
| Campo | Contenido |
|---|
| ID | AP-<FAMILIA>-<NN> para antipatrón, P-<FAMILIA>-<NN> para patrón a adoptar. Vocabulario cerrado. |
| Severidad | CRÍTICA · ALTA · MEDIA · BAJA, con el criterio objetivo de abajo. |
| Síntoma detectable | Qué se observa en el repositorio. En las fichas P-* el síntoma es la ausencia. |
| Detección | Comando ejecutable. Si no devuelve nada, el veredicto es LIMPIO. |
| Por qué duele | Consecuencia operativa concreta. Sin juicio de valor: se describe qué pasa, no si está bien o mal. |
| Corrección | Acción exacta. |
| MALO / BUENO | Par de ejemplos mínimos. |
| Fuente | URL canónica + versión mínima del CLI cuando aplica. |
2.1 Escala de severidad
- CRÍTICA — la configuración crea una barrera de seguridad que el usuario cree tener y no existe. El operador actúa confiando en un control inexistente.
- ALTA — la configuración no hace lo que declara: falla silenciosa, sin error visible.
- MEDIA — degrada calidad, coste o contexto sin romper nada.
- BAJA — higiene o deuda de mantenimiento.
Forma canónica del literal. En prosa, tablas y fichas de este capítulo la severidad más alta se escribe CRÍTICA, con tilde. En el campo severidad del JSON del informe (§8.1) el valor canónico es CRITICA, sin tilde: los cuatro valores admitidos son exactamente CRITICA, ALTA, MEDIA y BAJA, en mayúsculas y solo ASCII, para que el bloque sea comparable con == sin normalizar acentos. Cualquier otro valor —incluido CRÍTICA con tilde— es inválido dentro del JSON.
2.2 Ciclo de evaluación por ficha
flowchart TD
A[Tomar ficha del catálogo] --> B{¿La versión instalada<br/>alcanza el campo Desde?}
B -->|No| NA[NO_APLICA]
B -->|Sí / no aplica versión| C[Ejecutar comando de detección]
C --> D{¿Salida no vacía<br/>o condición cumplida?}
D -->|No| L[LIMPIO]
D -->|Sí| E{¿Hay evidencia citable:<br/>ruta + línea + texto literal?}
E -->|No| NV[NO_VERIFICABLE]
E -->|Sí| F[HALLAZGO]
F --> G[Asignar severidad de la ficha]
G --> H[Acumular en hallazgos y<br/>evaluar capadores §7]
NA --> H
L --> H
NV --> H
H --> I[Siguiente ficha]
§ 3 — Catálogo, parte I: instrucciones y control
3.1 Familia MEM — CLAUDE.md, rules y memoria
Contexto de familia: la jerarquía de memoria carga de más amplio a más específico y todo se concatena, nunca se sobrescribe: política gestionada → ~/.claude/CLAUDE.md → ./CLAUDE.md o ./.claude/CLAUDE.md → ./CLAUDE.local.md. Los CLAUDE.md de subdirectorios cargan bajo demanda al leer archivos de ahí (https://code.claude.com/docs/en/memory).
AP-MEM-01 — CLAUDE.md de más de 200 líneas
| Campo | Valor |
|---|
| Severidad | MEDIA (capador: techo CON RESERVAS, §7) |
| Síntoma | Cualquier archivo de memoria efectivo supera las 200 líneas. |
| Detección | wc -l CLAUDE.md .claude/CLAUDE.md ~/.claude/CLAUDE.md 2>/dev/null |
| Por qué duele | «Bloated CLAUDE.md files cause Claude to ignore your actual instructions!» El diagnóstico oficial es directo: «If Claude keeps doing something you don’t want despite having a rule against it, the file is probably too long and the rule is getting lost». |
| Corrección | Podar con el criterio «¿quitar esta línea haría que Claude se equivoque?». Mover lo ocasional a skills y lo path-scoped a .claude/rules/*.md. |
| Fuente | https://code.claude.com/docs/en/best-practices |
<!-- MALO: 430 líneas describiendo archivo por archivo -->
## src/components/Button.tsx
Componente de botón. Recibe props variant y size...
## src/components/Card.tsx
Componente de tarjeta. Recibe props title y footer...
<!-- BUENO: solo lo no deducible del código -->
## Comandos
- `bun dev` (no `npm run dev`: el proyecto usa Bun como runtime)
- Tests: `bun test --bail` — el runner falla si no pasás --bail en CI
## Gotchas
- El adaptador de Astro es standalone: `bun build` no sirve, usar `./deploy.sh`
AP-MEM-02 — Contenido deducible del código o consejos genéricos
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Líneas tipo «write clean code», «usa nombres descriptivos», descripciones archivo-por-archivo, documentación de API detallada, convenciones estándar del lenguaje. |
| Detección | grep -niE 'clean code|buenas prácticas|código legible|usa nombres descriptivos|sigue las convenciones' CLAUDE.md .claude/CLAUDE.md ~/.claude/CLAUDE.md 2>/dev/null |
| Por qué duele | Consume presupuesto de atención sin cambiar comportamiento; es el combustible del AP-MEM-01. |
| Corrección | Eliminar. La doc lista explícitamente qué excluir: lo deducible leyendo el código, convenciones estándar, docs de API detalladas (enlazar), información que cambia seguido, descripciones archivo-por-archivo y obviedades. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-MEM-03 — AGENTS.md sin @AGENTS.md ni symlink
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Existe AGENTS.md en la raíz y no existe un CLAUDE.md que lo importe con @AGENTS.md, ni CLAUDE.md es un symlink a él. |
| Detección | test -f AGENTS.md && ! grep -q '@AGENTS.md' CLAUDE.md 2>/dev/null && ! test -L CLAUDE.md && echo "HALLAZGO AP-MEM-03" |
| Por qué duele | Claude Code no lee AGENTS.md en runtime. El equipo cree tener instrucciones cargadas y el agente nunca las ve: falla silenciosa total. |
| Corrección | Crear CLAUDE.md con una línea @AGENTS.md, o hacer ln -s AGENTS.md CLAUDE.md. |
| Fuente | https://code.claude.com/docs/en/memory |
<!-- MALO: AGENTS.md solo, sin puente -->
(el repo tiene AGENTS.md con 200 líneas de reglas y ningún CLAUDE.md)
<!-- BUENO: CLAUDE.md mínimo que importa AGENTS.md -->
@AGENTS.md
AP-MEM-04 — Trocear en @imports creyendo que reduce contexto
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | CLAUDE.md corto que importa media docena de archivos grandes, con comentarios del tipo «dividido para no cargar todo». |
| Detección | grep -c '^@' CLAUDE.md; grep -o '^@.*' CLAUDE.md | tr -d '@' | xargs -r wc -l 2>/dev/null |
| Por qué duele | Los imports @ruta se expanden en contexto al inicio (máximo 4 niveles de profundidad). Dividir organiza, pero no reduce ni un token. El presupuesto real es la suma de todos los archivos importados. |
| Corrección | Si el objetivo es carga condicional, usar .claude/rules/*.md con frontmatter paths: o skills; los imports quedan solo para organización. |
| Fuente | https://code.claude.com/docs/en/memory |
AP-MEM-05 — Regla crítica confiada a CLAUDE.md
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | CLAUDE.md contiene reglas de seguridad u operación redactadas como garantía absoluta: «NUNCA ejecutes migraciones en producción», «JAMÁS toques .env», «PROHIBIDO hacer push a main». |
| Detección | grep -niE 'NUNCA|JAMÁS|PROHIBIDO|NEVER|bajo ninguna circunstancia' CLAUDE.md .claude/CLAUDE.md ~/.claude/CLAUDE.md 2>/dev/null |
| Por qué duele | CLAUDE.md se entrega como mensaje de usuario tras el system prompt: es advisory, no configuración forzada. El operador cree tener un control duro que no existe. Para garantías deterministas la vía documentada son hooks o permissions.deny. |
| Corrección | Trasladar la regla a permissions.deny (enforcement duro) o a un hook PreToolUse con exit 2. Se puede dejar la línea en CLAUDE.md como refuerzo, nunca como único control. |
| Fuente | https://code.claude.com/docs/en/memory |
<!-- MALO: única barrera, en CLAUDE.md -->
NUNCA ejecutes `prisma migrate deploy` contra la base de producción.
// BUENO: barrera dura en .claude/settings.json
{
"permissions": {
"deny": ["Bash(prisma migrate deploy *)", "Bash(* --env=production *)"]
}
}
AP-MEM-06 — Conocimiento path-scoped fuera de .claude/rules/
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | CLAUDE.md contiene secciones que solo aplican a una parte del repo («cuando toques infra/terraform…», «para los tests de apps/api…») y no existe .claude/rules/. |
| Detección | ls .claude/rules/*.md 2>/dev/null || grep -niE 'cuando (toques|edites|trabajes en) ' CLAUDE.md 2>/dev/null |
| Por qué duele | Esa sección se carga en todas las sesiones, incluidas las que nunca tocan ese directorio. |
| Corrección | Mover a .claude/rules/<tema>.md con frontmatter paths: (globs). Las reglas sin paths cargan al inicio con la misma prioridad que .claude/CLAUDE.md. |
| Fuente | https://code.claude.com/docs/en/memory |
<!-- BUENO: .claude/rules/terraform.md -->
---
paths:
- "infra/terraform/**"
---
- El backend de estado es S3 con lock en DynamoDB: nunca correr `terraform init -reconfigure`.
- Los `plan` se guardan como artefacto antes de cualquier `apply`.
AP-MEM-07 — CLAUDE.local.md en flujo con worktrees
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Existe CLAUDE.local.md y el repo usa worktrees (git worktree list devuelve más de una entrada, o hay .claude/worktrees/). |
| Detección | test -f CLAUDE.local.md && git worktree list | wc -l |
| Por qué duele | El archivo vive en un solo worktree: el resto de los worktrees corren sin esas instrucciones. |
| Corrección | Sustituirlo por un import a un archivo del home: @~/.claude/mi-archivo.md, que es la recomendación explícita de la doc para worktrees. |
| Fuente | https://code.claude.com/docs/en/memory |
AP-MEM-08 — Asumir que un CLAUDE.md anidado se reinyecta tras /compact
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Reglas importantes viven solo en un CLAUDE.md de subdirectorio (por ejemplo packages/api/CLAUDE.md) y el flujo de trabajo usa /compact en sesiones largas. |
| Detección | find . -name CLAUDE.md -mindepth 2 -not -path './node_modules/*' |
| Por qué duele | El CLAUDE.md de la raíz sobrevive a /compact (se relee de disco y se reinyecta); los anidados no se reinyectan y solo recargan cuando Claude vuelve a leer un archivo de ese subdirectorio. Tras compactar, la regla desaparece sin aviso. |
| Corrección | Subir a la raíz lo que debe sobrevivir a compactación, o convertirlo en una regla .claude/rules/*.md con paths:. |
| Fuente | https://code.claude.com/docs/en/memory |
AP-MEM-09 — Auto memory asumida como heredada por subagentes
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Subagentes cuyo prompt da por sabido algo que solo está en ~/.claude/projects/<project>/memory/; o MEMORY.md que supera 200 líneas / 25KB. |
| Detección | wc -lc ~/.claude/projects/*/memory/MEMORY.md 2>/dev/null |
| Por qué duele | La auto memory está activada por defecto y del índice MEMORY.md se cargan solo las primeras 200 líneas o 25KB por sesión; los archivos de tema se leen bajo demanda. Además, la auto memory del hilo principal no se pasa a los subagentes: un subagente arranca sin ese conocimiento. |
| Corrección | Escribir el contexto necesario en el cuerpo markdown del subagente (su system prompt) o en el prompt de delegación; para lo que deba sobrevivir entre invocaciones, darle memoria propia con el campo memory: (user, project o local). No sirve initialPrompt: solo se auto-envía cuando el agente corre como agente de la sesión principal (--agent o el setting agent), no cuando la Agent tool lo invoca como subagente; y description define cuándo delegar, no el contexto. Además, podar MEMORY.md por debajo del corte. Se desactiva con autoMemoryEnabled: false o CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 si no se usa. |
| Fuente | https://code.claude.com/docs/en/memory, https://code.claude.com/docs/en/sub-agents |
Enrutamiento de una instrucción
flowchart TD
A[Tengo una instrucción<br/>para el agente] --> B{¿Aplica SIEMPRE,<br/>en toda sesión?}
B -->|Sí| C{¿Debe cumplirse<br/>sin excepción posible?}
B -->|No| D{¿Es conocimiento de dominio<br/>o un flujo ocasional?}
C -->|Sí| E[permissions.deny<br/>o hook con exit 2]
C -->|No, es orientación| F[CLAUDE.md<br/>menos de 200 líneas]
D -->|Sí| G[Skill:<br/>.claude/skills/x/SKILL.md]
D -->|Aplica solo a ciertas rutas| H[.claude/rules/x.md<br/>con frontmatter paths:]
Regla canónica: CLAUDE.md solo para lo que aplica siempre; skills para conocimiento de dominio o flujos ocasionales, que cargan bajo demanda «without bloating every conversation»; hooks o permissions.deny para lo que debe ocurrir sin excepción, porque CLAUDE.md es advisory (https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/memory).
3.2 Familia PERM — Reglas de permisos
Es la familia más densa del catálogo y la que produce más fallas silenciosas. Los tres hechos que estructuran todo lo demás:
- Las reglas
allow/ask/deny siguen la misma precedencia de settings que el resto de la configuración («Permission rules follow the same settings precedence as all other Claude Code settings»). Lo que cambia no es la precedencia sino la forma de combinar: al ser settings con valor de array se concatenan y deduplican entre scopes en vez de reemplazarse. El efecto práctico es que un deny de cualquier scope bloquea un allow de cualquier otro, incluido --allowedTools, «because deny rules from any scope are evaluated before allow rules».
- El orden de evaluación es deny → ask → allow y la primera coincidencia decide. La especificidad no altera nada.
- Los protected paths se chequean antes de evaluar
allow.
Fuente transversal de la familia: https://code.claude.com/docs/en/permissions y https://code.claude.com/docs/en/permission-modes.
AP-PERM-01 — Asumir que un scope superior anula un deny
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | .claude/settings.local.json (o --allowedTools) intenta “sobrescribir” un deny definido en ~/.claude/settings.json o en managed. |
| Detección | jq -r '.permissions.allow[]?' .claude/settings.local.json 2>/dev/null y cruzar con jq -r '.permissions.deny[]?' ~/.claude/settings.json /etc/claude-code/managed-settings.json 2>/dev/null |
| Por qué duele | Las reglas de permisos se fusionan entre scopes y la evaluación es deny → ask → allow, así que un deny de cualquier scope gana sobre cualquier allow. La regla nunca aplica y no hay error visible: el usuario cree haber habilitado algo que sigue bloqueado. |
| Corrección | Quitar el deny en su origen o aceptar el bloqueo. No existe forma de anularlo desde otro scope, ni siquiera con --allowedTools frente a un deny de managed settings. |
| Fuente | https://code.claude.com/docs/en/permissions#settings-precedence |
AP-PERM-02 — Excepción dentro de un deny
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Un deny amplio y un allow más específico que pretende ser su excepción. |
| Detección | jq -r '.permissions | (.deny[]? | "DENY "+.), (.allow[]? | "ALLOW "+.)' .claude/settings.json 2>/dev/null | sort y buscar prefijos comunes. |
| Por qué duele | «No hay excepciones dentro de un deny»: Bash(aws *) en deny bloquea Bash(aws s3 ls) aunque esté en allow. |
| Corrección | Invertir la lógica: acotar el deny a lo que realmente se quiere bloquear (Bash(aws ec2 terminate-instances *)) y dejar el allow específico. |
| Fuente | https://code.claude.com/docs/en/permissions |
// MALO: el allow nunca se alcanza
{ "permissions": { "deny": ["Bash(aws *)"], "allow": ["Bash(aws s3 ls *)"] } }
// BUENO: deny acotado a lo destructivo
{ "permissions": {
"deny": ["Bash(aws ec2 terminate-instances *)", "Bash(aws s3 rb *)"],
"allow": ["Bash(aws s3 ls *)"] } }
AP-PERM-03 — Write(...), NotebookEdit(...) o Glob(...) en reglas de ruta
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: REPROBADO) · Desde v2.1.210 |
| Síntoma | Reglas de permisos con especificador de ruta sobre Write, NotebookEdit o Glob. |
| Detección | grep -rnE '"(Write|NotebookEdit|Glob)\(' .claude/ ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Desde v2.1.210 solo Edit(path) y Read(path) se evalúan en chequeos de permisos de archivo. Write(path), NotebookEdit(path) y Glob(path) se aceptan pero nunca coinciden (emiten warning de arranque). Un deny: ["Write(secrets/**)"] es una barrera imaginaria. |
| Corrección | Reescribir con Edit(...) y Read(...). Nota: desde v2.1.208 un deny de Read bloquea también Edit sobre esa ruta, así que Read suele ser suficiente para proteger. |
| Fuente | https://code.claude.com/docs/en/permissions |
// MALO: nunca coincide, solo genera warning
{ "permissions": { "deny": ["Write(//etc/**)", "Glob(secrets/**)"] } }
// BUENO
{ "permissions": { "deny": ["Read(//etc/**)", "Edit(//etc/**)"] } }
AP-PERM-04 — /ruta creída absoluta
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: REPROBADO) |
| Síntoma | Reglas con una sola barra inicial que pretenden apuntar a la raíz del filesystem: Read(/etc/**), Edit(/Users/alice/**). |
| Detección | grep -rnE '"(Read|Edit)\(/[^/]' .claude/ ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Las rutas siguen la especificación gitignore con cuatro anclajes: //path = absoluta desde la raíz del filesystem; ~/path = home; /path = relativa al origen del settings (project root, ~/.claude, …); path o ./path = relativa al cwd. Es decir, /Users/alice/file no es absoluta: resuelve contra el directorio del settings y no protege nada. |
| Corrección | Doble barra para absolutas: Read(//etc/**). |
| Fuente | https://code.claude.com/docs/en/permissions |
// MALO: resuelve a <project>/etc/**
{ "permissions": { "deny": ["Read(/etc/shadow)"] } }
// BUENO: raíz del filesystem
{ "permissions": { "deny": ["Read(//etc/shadow)"] } }
AP-PERM-05 — Bash(ls*) sin frontera de palabra
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Patrones Bash sin espacio antes del asterisco en reglas allow. |
| Detección | jq -r '.permissions.allow[]? | select(test("^Bash\\([a-zA-Z0-9_-]+\\*"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | El espacio antes del * impone frontera de palabra: Bash(ls *) matchea ls -la pero no lsof; Bash(ls*) matchea ambos. Un allow sin frontera aprueba comandos que el autor nunca consideró. |
| Corrección | Escribir Bash(ls *) o el sufijo equivalente Bash(ls:*), que solo se reconoce al final del patrón. |
| Fuente | https://code.claude.com/docs/en/permissions |
AP-PERM-06 — allow sobre runners no despojados
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | allow que contiene Bash(devbox run *), Bash(npx *), Bash(docker exec *), Bash(mise exec *), Bash(direnv exec *). |
| Detección | jq -r '.permissions.allow[]? | select(test("npx |docker exec|devbox run|mise exec|direnv exec"))' .claude/settings.json ~/.claude/settings.json .claude/settings.local.json 2>/dev/null |
| Por qué duele | Claude Code elimina wrappers antes de evaluar, pero la lista es interna y no configurable: timeout, time, nice, nohup, stdbuf, los builtins command y builtin, el noglob de zsh y xargs sin flags. No elimina runners como npx, docker exec, devbox run, mise exec o direnv exec. Por lo tanto Bash(devbox run *) autoriza cualquier comando interno, incluido devbox run rm -rf .. |
| Corrección | Nunca pre-aprobar el runner en genérico. Aprobar comandos concretos: Bash(devbox run test *), Bash(npx prettier *). |
| Fuente | https://code.claude.com/docs/en/permissions |
// MALO: allow universal encubierto
{ "permissions": { "allow": ["Bash(npx *)", "Bash(docker exec *)"] } }
// BUENO
{ "permissions": { "allow": ["Bash(npx prettier --write *)", "Bash(docker exec ci-runner pytest *)"] } }
AP-PERM-07 — Asumir que Bash(safe *) cubre safe && otro
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Allowlist diseñada suponiendo que aprobar un comando aprueba la cadena entera. |
| Detección | Revisión manual de .permissions.allow + los comandos reales del historial. |
| Por qué duele | Bash(safe-cmd *) no autoriza safe-cmd && other-cmd. Los separadores reconocidos son &&, ||, ;, |, |&, & y saltos de línea, y cada subcomando se evalúa por separado. El efecto práctico es lo contrario del temido: no hay escape, hay prompts inesperados. |
| Corrección | Añadir reglas por cada subcomando que se quiera pre-aprobar. En PowerShell rige la misma forma, con canonicalización de alias (gci, ls, dir → Get-ChildItem). |
| Fuente | https://code.claude.com/docs/en/permissions |
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Entradas tipo Agent(model:opus) o Bash(run_in_background:true) en la lista allow. |
| Detección | jq -r '.permissions.allow[]? | select(test("\\([a-zA-Z_]+:"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null | grep -v '^Bash(.*:\*)$' |
| Por qué duele | El match por parámetro Tool(param:value) solo funciona en deny y ask, nunca en allow. Además, los campos con canonicalización propia (command, file_path, path, url, notebook_path) se ignoran en esta forma y emiten warning de arranque. |
| Corrección | Mover la regla a deny o ask, que es donde se evalúa. |
| Fuente | https://code.claude.com/docs/en/permissions |
AP-PERM-09 — Glob no anclado en el nombre de herramienta
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | allow con comodines en el nombre de la herramienta: "*", "mcp__*", "Web*". |
| Detección | jq -r '.permissions.allow[]? | select(test("^[^(]*\\*"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | deny y ask aceptan globs completos en el nombre de herramienta; allow solo acepta glob tras el prefijo literal mcp__<server>__. Un allow no anclado se descarta con warning: el usuario cree haber pre-aprobado un conjunto y sigue recibiendo prompts (o peor, asume que su automatización headless no se va a colgar). |
| Corrección | Anclar: mcp__github__* es válido en allow; mcp__* no. |
| Fuente | https://code.claude.com/docs/en/permissions |
AP-PERM-10 — WebFetch(domain:*.example.com) sin cubrir el apex
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Reglas WebFetch(domain:*.dominio.tld) sin la entrada correspondiente para dominio.tld. |
| Detección | jq -r '.permissions | (.allow[]?,.deny[]?,.ask[]?) | select(startswith("WebFetch(domain:*."))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | WebFetch(domain:*.example.com) cubre subdominios a cualquier profundidad pero no example.com. En un deny, eso deja el apex abierto; en un allow, produce prompts inesperados. Fuera de un *. inicial o un * solo, el wildcard no cruza puntos: example.* matchea example.org pero no example.evil.com. |
| Corrección | Declarar ambas formas: WebFetch(domain:example.com) y WebFetch(domain:*.example.com). |
| Fuente | https://code.claude.com/docs/en/permissions |
AP-PERM-11 — defaultMode: "auto" en settings de proyecto
| Campo | Valor |
|---|
| Severidad | ALTA · Desde v2.1.142 |
| Síntoma | .claude/settings.json o .claude/settings.local.json con permissions.defaultMode: "auto". |
| Detección | jq -r '.permissions.defaultMode' .claude/settings.json .claude/settings.local.json 2>/dev/null |
| Por qué duele | Desde v2.1.142 ese valor se ignora cuando viene de settings de proyecto o local, para que un repo no se autoconceda auto mode. El equipo cree operar en auto y opera en default, con todos los prompts. |
| Corrección | Declararlo en ~/.claude/settings.json o en managed settings. |
| Fuente | https://code.claude.com/docs/en/permission-modes |
AP-PERM-12 — allow para pre-aprobar protected paths
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | allow con rutas dentro de .git, .claude, .vscode, .idea, .husky, .cargo, .devcontainer, .yarn, .mvn, .config/git, o los archivos .bashrc, .zshrc, .envrc, .npmrc, .mcp.json, .claude.json. |
| Detección | jq -r '.permissions.allow[]? | select(test("\\.git|\\.claude|\\.vscode|\\.idea|\\.husky|\\.cargo|\\.devcontainer|\\.yarn|\\.mvn|bashrc|zshrc|envrc|npmrc"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Los protected paths nunca se auto-aprueban salvo en bypassPermissions: el chequeo corre antes de evaluar allow. La regla no hace nada, y su presencia sugiere que alguien diseñó una automatización asumiendo que esas escrituras no iban a preguntar. |
| Corrección | Eliminar la regla y aceptar la aprobación manual, o mover ese trabajo a un entorno aislado. Ojo: .claude/worktrees es la única excepción dentro de .claude. |
| Fuente | https://code.claude.com/docs/en/permission-modes |
AP-PERM-13 — Edit(src/**) esperando profundidad arbitraria
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: REPROBADO si la regla es deny/ask) · Aplica a cualquier versión en reglas de permisos; en el campo if de hooks, desde v2.1.214 |
| Síntoma | Patrones de un solo segmento inicial: Edit(src/**), Read(secrets/**), o el campo if de un hook con la misma forma. |
| Detección | grep -rnE '"(Read|Edit)\([a-zA-Z0-9_.-]+/\*\*\)' .claude/ ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Un patrón de un solo segmento coincide solo con ese directorio en el directorio de trabajo. Edit(src/**) no cubre packages/api/src/. En reglas de permisos el anclaje estilo gitignore siempre ha sido así: la regla está mal escrita en cualquier versión del CLI, y en un monorepo deja fuera la mayoría del árbol. Lo que cambió en v2.1.214 es el campo if de hooks, que antes hacía coincidir un directorio src a cualquier profundidad y ahora se comporta igual que los permisos. |
| Corrección | Escribir Edit(**/src/**) para cualquier profundidad. Nunca marcar NO_APLICA por versión del CLI cuando el patrón está en permissions. |
| Fuente | https://code.claude.com/docs/en/permissions · https://code.claude.com/docs/en/hooks#common-fields |
// MALO: solo ./secrets, no packages/*/secrets
{ "permissions": { "deny": ["Read(secrets/**)"] } }
// BUENO
{ "permissions": { "deny": ["Read(**/secrets/**)"] } }
AP-PERM-14 — bypassPermissions como modo habitual fuera de contenedor
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: REPROBADO) |
| Síntoma | permissions.defaultMode: "bypassPermissions" en cualquier settings, o --dangerously-skip-permissions en scripts, Makefiles, package.json o workflows de CI, sin evidencia de contenedor o VM. |
| Detección | grep -rn 'bypassPermissions|dangerously-skip-permissions' . --include='*.json' --include='*.sh' --include='*.yml' --include='*.yaml' --include='Makefile' 2>/dev/null | grep -v node_modules |
| Por qué duele | Salta los prompts incluso para escrituras en los protected paths, es decir, permite auto-escalada: el agente puede reescribir .claude/ y ampliar sus propios permisos. La doc es explícita: «Only use this mode in isolated environments like containers or VMs». Frenos que sí quedan: reglas ask explícitas, herramientas de conectores marcadas ask por la organización, herramientas MCP con requiresUserInteraction, y el circuit breaker de borrados sobre raíz/home (rm -rf /, rm -rf ~), que desde v2.1.208 cubre también esas rutas ocultas tras $(...), backticks o <(...). |
| Corrección | Sustituir por el trío vigente (P-SEC-01): auto mode + allowlist vía /permissions + sandbox OS-level vía /sandbox. Si se necesita autonomía real, correr en dev container con usuario no-root: en Linux y macOS el CLI se niega a arrancar con esa flag bajo root/sudo, salvo dentro de un sandbox reconocido. A nivel organización, bloquear con permissions.disableBypassPermissionsMode: "disable". |
| Fuente | https://code.claude.com/docs/en/permissions, https://code.claude.com/docs/en/permission-modes |
AP-PERM-15 — deny desnudo vs. con especificador, usado al revés
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | deny: ["Bash"] cuando se quería bloquear solo algunos comandos, o deny: ["Bash(rm *)"] cuando se quería quitar la herramienta entera. |
| Detección | jq -r '.permissions.deny[]? | select(test("^[A-Za-z_]+$"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Un deny con nombre desnudo (Bash) elimina la herramienta del contexto del modelo; con especificador (Bash(rm *)) la deja disponible y bloquea solo las coincidencias. Confundirlos produce o un agente mutilado o una barrera más laxa de lo previsto. EndConversation es la excepción documentada: ningún deny/ask la quita mientras quede otra herramienta. |
| Corrección | Elegir la forma según la intención y documentarla en el propio settings. |
| Fuente | https://code.claude.com/docs/en/permissions |
AP-PERM-16 — Esperar que additionalDirectories cargue skills y subagentes
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | permissions.additionalDirectories apuntando a un repo de skills o subagentes compartidos. |
| Detección | jq -r '.permissions.additionalDirectories[]?' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | additionalDirectories concede solo acceso a archivos: no carga skills ni subagentes, a diferencia de --add-dir. Además, permissions.allow y additionalDirectories de un settings de proyecto solo aplican tras aceptar el diálogo de workspace trust (deny y ask no lo requieren). |
| Corrección | Usar --add-dir para cargar extensibilidad, o un plugin/marketplace para distribuir skills y agentes. |
| Fuente | https://code.claude.com/docs/en/permissions |
Orden real de evaluación
flowchart TD
A[Claude solicita una acción] --> B{¿Escritura sobre<br/>un protected path?}
B -->|Sí y el modo no es<br/>bypassPermissions| P[Prompt obligatorio<br/>allow NO lo pre-aprueba]
B -->|No| C{¿Coincide algún deny<br/>de CUALQUIER scope?}
C -->|Sí| D[Bloqueado · sin excepciones]
C -->|No| E{¿Coincide algún ask?}
E -->|Sí| F[Pregunta al usuario]
E -->|No| G{¿Coincide algún allow<br/>válido y anclado?}
G -->|Sí| H[Ejecuta sin preguntar]
G -->|No| I[Decide el modo:<br/>default / acceptEdits / auto /<br/>dontAsk / plan / bypassPermissions]
3.3 Familia SBX — Sandbox y aislamiento
El sandbox corre en macOS (Seatbelt), Linux y WSL2 (bubblewrap + socat). Por defecto permite escritura solo en el cwd y en el $TMPDIR de sesión, pero lectura de todo el equipo salvo directorios denegados (https://code.claude.com/docs/en/sandboxing).
AP-SBX-01 — Sandbox sin failIfUnavailable: true
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: CON RESERVAS en flujo autónomo) |
| Síntoma | sandbox.enabled: true sin sandbox.failIfUnavailable: true. |
| Detección | jq -r '.sandbox | {enabled, failIfUnavailable}' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Falla abierto: si el sandbox no arranca, por defecto se emite un warning y los comandos corren sin sandbox. En una sesión autónoma nadie lee ese warning y el aislamiento que se creía activo no existe. |
| Corrección | Añadir "failIfUnavailable": true. |
| Fuente | https://code.claude.com/docs/en/sandboxing |
// MALO
{ "sandbox": { "enabled": true } }
// BUENO
{ "sandbox": { "enabled": true, "failIfUnavailable": true } }
AP-SBX-02 — Aplicar la convención // de permisos a sandbox.filesystem.*
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Rutas con doble barra inicial dentro de sandbox.filesystem.*. |
| Detección | jq -r '.sandbox.filesystem | .. | strings | select(startswith("//"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | sandbox.filesystem.* usa convenciones estándar y distintas a las de permisos: /tmp/build es absoluta, ~/ es home, y ./ o sin prefijo resuelve contra la raíz del proyecto (settings de proyecto) o contra ~/.claude (settings de usuario). El // de Read/Edit no se usa aquí, y una ruta mal escrita deja de proteger sin error. |
| Corrección | Escribir absolutas con una sola barra en esta sección. Nota: sandbox.filesystem.disabled: true (v2.1.216+) apaga el aislamiento de filesystem manteniendo el de red, y solo se honra desde user/managed/--settings. |
| Fuente | https://code.claude.com/docs/en/sandboxing |
AP-SBX-03 — network.allowedDomains amplios sin tlsTerminate
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Entradas comodín amplias (*.amazonaws.com, *.googleapis.com, *.github.io) en sandbox.network.allowedDomains sin network.tlsTerminate. |
| Detección | jq -r '.sandbox.network | {allowedDomains, tlsTerminate}' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | El proxy decide por hostname y no termina TLS por defecto, así que un dominio amplio habilita domain fronting. network.tlsTerminate (experimental, v2.1.199+) es un objeto ({} para CA efímera, o caCertPath + caKeyPath) que hace que el proxy termine TLS, pero no filtra contenido. |
| Corrección | Enumerar hosts concretos. Recordar que ningún dominio viene pre-permitido: el proxy pide aprobación en el primer uso y, desde v2.1.191, un «Yes» permite ese host el resto de la sesión. |
| Fuente | https://code.claude.com/docs/en/sandboxing |
AP-SBX-04 — Suponer que el sandbox protege credenciales por defecto
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | sandbox.enabled: true sin bloque sandbox.credentials, en un entorno con .env, ~/.aws/credentials, tokens en variables de entorno. |
| Detección | jq -r 'has("sandbox") and (.sandbox | has("credentials"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Por defecto el sandbox permite lectura de todo el equipo y no trae lista de credenciales denegadas. El operador cree que “está sandboxeado” y las claves siguen legibles. |
| Corrección | Declarar sandbox.credentials (v2.1.187+) con files[].mode: "deny" y envVars[].mode: "deny" o "mask". El modo mask (v2.1.199+) sustituye la credencial por un sentinel por sesión y el proxy inyecta el valor real solo hacia los injectHosts; requiere network.tlsTerminate, que es un objeto: {} genera una CA efímera para la sesión, o se pasan caCertPath y caKeyPath para usar una propia. Cada host de injectHosts debe estar cubierto por network.allowedDomains. Tanto mask como tlsTerminate se ignoran si vienen de settings de proyecto o local. |
| Fuente | https://code.claude.com/docs/en/sandboxing |
// BUENO: en ~/.claude/settings.json
{ "sandbox": {
"enabled": true,
"failIfUnavailable": true,
"network": {
"tlsTerminate": {},
"allowedDomains": ["api.github.com"]
},
"credentials": {
"files": [{ "path": "~/.aws/credentials", "mode": "deny" }],
"envVars": [{ "name": "GITHUB_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] }]
} } }
AP-SBX-05 — Sandbox en Windows nativo o WSL1
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | sandbox.enabled: true en una máquina Windows nativa o WSL1. |
| Detección | uname -a; jq -r '.sandbox.enabled' ~/.claude/settings.json 2>/dev/null |
| Por qué duele | El sandbox corre en macOS (Seatbelt), Linux y WSL2; Windows nativo y WSL1 no están soportados. Sin failIfUnavailable: true la sesión corre sin aislamiento y solo deja un warning. |
| Corrección | Migrar a WSL2 o a un contenedor, o asumir explícitamente la ausencia de sandbox y compensar con permissions.deny. |
| Fuente | https://code.claude.com/docs/en/settings#sandbox-settings |
3.4 Familia SET — settings.json, scopes y control empresarial
Precedencia de settings (mayor a menor): 1) managed settings, 2) argumentos de línea de comandos, 3) .claude/settings.local.json, 4) .claude/settings.json, 5) ~/.claude/settings.json. Las reglas de permisos siguen esta misma precedencia; la diferencia está en cómo se combinan los valores: los settings con valor de array (permissions.allow/ask/deny, sandbox.filesystem.allowWrite, etc.) se concatenan y deduplican entre scopes en vez de sobrescribirse, y como la evaluación es deny → ask → allow, un deny de cualquier scope gana sobre un allow de cualquier otro (§3.2). Las dos excepciones al merge de arrays son fallbackModel y availableModels. Fuente: https://code.claude.com/docs/en/settings#settings-precedence y https://code.claude.com/docs/en/permissions#settings-precedence.
AP-SET-01 — Managed settings en C:\ProgramData\ClaudeCode\
| Campo | Valor |
|---|
| Severidad | ALTA · Eliminada en v2.1.75 |
| Síntoma | Documentación interna, scripts de provisioning o MDM que despliegan a C:\ProgramData\ClaudeCode\managed-settings.json. |
| Detección | grep -rn 'ProgramData\\\\ClaudeCode|ProgramData/ClaudeCode' . 2>/dev/null | grep -v node_modules |
| Por qué duele | Esa ruta quedó deprecada y eliminada en v2.1.75. La política de la organización simplemente no se aplica, sin error. |
| Corrección | Migrar a C:\Program Files\ClaudeCode\managed-settings.json (lo mismo para el CLAUDE.md de política y para managed-mcp.json). |
| Fuente | https://code.claude.com/docs/en/settings |
AP-SET-02 — Claves managed-only en user o project
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Claves como allowManagedHooksOnly, strictPluginOnlyCustomization, allowManagedPermissionRulesOnly, allowManagedMcpServersOnly, blockedMarketplaces, disableSideloadFlags en ~/.claude/settings.json o .claude/settings.json. |
| Detección | jq -r 'keys[] | select(test("^(allowManaged|strict|blockedMarketplaces|channelsEnabled|disableSideloadFlags|forceRemoteSettingsRefresh|pluginTrustMessage|allowAllClaudeAiMcps|allowedChannelPlugins|wslInheritsWindowsSettings)"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Estas claves no tienen efecto fuera de managed settings. El endurecimiento que el equipo cree haber aplicado no existe. |
| Corrección | Moverlas a managed settings. Excepción a tener presente: permissions.disableBypassPermissionsMode sí funciona desde cualquier scope, aunque en managed además no puede sobrescribirse ni con flags de CLI. |
| Fuente | https://code.claude.com/docs/en/permissions, https://code.claude.com/docs/en/settings |
AP-SET-03 — Asumir merge de fallbackModel
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | fallbackModel definido en varios scopes esperando que se sumen, o con más de tres entradas. |
| Detección | jq -r '.fallbackModel' .claude/settings.json ~/.claude/settings.json .claude/settings.local.json 2>/dev/null |
| Por qué duele | Los arrays de settings se fusionan y deduplican entre scopes, salvo fallbackModel (máximo 3, las entradas extra se ignoran), que no se fusiona: el archivo de mayor precedencia que lo define aporta la cadena completa y el resto se descarta. |
| Corrección | Declararlo en un único scope, con hasta tres modelos. |
| Fuente | https://code.claude.com/docs/en/settings, https://code.claude.com/docs/en/model-config#fallback-model-chains |
AP-SET-04 — Secretos o rutas de máquina en .claude/settings.json versionado
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | env, headers o rutas absolutas de una máquina concreta dentro del settings versionado. |
| Detección | git ls-files .claude/settings.json >/dev/null && jq -r '.env // {} | keys[]' .claude/settings.json 2>/dev/null; grep -nE '"(sk-|ghp_|xoxb-|AKIA)' .claude/settings.json 2>/dev/null |
| Por qué duele | Queda en el historial de git y en cada clone. Si el valor es un token, el hallazgo se reporta con el nombre de la clave y [REDACTADO] (regla 7 de §1.1). Las rutas de máquina, además, rompen la config del resto del equipo. |
| Corrección | Mover secretos a variables de entorno, headersHelper (MCP) o el keychain vía OAuth; mover rutas de máquina a .claude/settings.local.json o a ~/.claude/settings.json. Rotar cualquier credencial ya commiteada. |
| Fuente | https://code.claude.com/docs/en/settings, https://code.claude.com/docs/en/mcp |
AP-SET-05 — .claude/settings.local.json commiteado o buscado fuera de la raíz del repo
| Campo | Valor |
|---|
| Severidad | ALTA · Desde v2.1.211 |
| Síntoma | El archivo está trackeado por git, o existen copias en subdirectorios donde se inician sesiones. |
| Detección | git ls-files --error-unmatch .claude/settings.local.json 2>/dev/null && echo "HALLAZGO: trackeado"; find . -name settings.local.json -not -path './node_modules/*' |
| Por qué duele | Desde v2.1.211 las aprobaciones «Yes, don’t ask again» se guardan en .claude/settings.local.json en la raíz del repo git (resuelta a través de worktrees), y ese archivo se carga desde la raíz aunque se inicie en un subdirectorio. Copias en subdirectorios no se leen; una copia commiteada distribuye aprobaciones personales a todo el equipo. Contexto histórico: entre v2.1.196 y v2.1.199 este archivo se trataba erróneamente como suministrado por el repo; se restauró en v2.1.200. |
| Corrección | Añadirlo a .gitignore, dejar una sola copia en la raíz del repo y borrar las de subdirectorios. |
| Fuente | https://code.claude.com/docs/en/permissions |
§ 4 — Catálogo, parte II: extensibilidad
4.1 Familia SKL — Skills y comandos
Hecho estructural de la familia: los comandos personalizados se fusionaron con las skills. .claude/commands/deploy.md y .claude/skills/deploy/SKILL.md generan ambos /deploy y comparten el mismo frontmatter; ante colisión de nombre gana la skill (https://code.claude.com/docs/en/slash-commands).
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: CON RESERVAS) |
| Síntoma | Un SKILL.md (o commands/*.md) declara allowed-tools y no declara disallowed-tools, con la intención evidente de limitar lo que la skill puede hacer. |
| Detección | grep -rln 'allowed-tools' .claude/skills .claude/commands ~/.claude/skills 2>/dev/null | xargs -r grep -L 'disallowed-tools' |
| Por qué duele | allowed-tools en una skill no restringe: pre-aprueba. Habilita esas herramientas sin pedir permiso durante el turno que invoca la skill, y caduca con el siguiente mensaje del usuario. Una skill escrita “para que solo pueda leer” termina con lecturas pre-aprobadas y todo lo demás intacto. Para restringir de verdad se usa disallowed-tools. |
| Corrección | Sustituir por disallowed-tools, o mantener ambos si además se quiere pre-aprobar. |
| Fuente | https://code.claude.com/docs/en/skills#frontmatter-reference |
# MALO: cree restringir, en realidad pre-aprueba
---
name: auditar
allowed-tools: Read, Grep, Bash
---
# BUENO: restricción real
---
name: auditar
disallowed-tools: Edit, Write, NotebookEdit
---
AP-SKL-02 — $1 usado como primer argumento
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Cuerpos de skill o comando que usan $1 esperando el primer argumento. |
| Detección | grep -rn '\$1\b' .claude/skills .claude/commands ~/.claude/skills 2>/dev/null |
| Por qué duele | $N es atajo de $ARGUMENTS[N] con índice base 0: $0 es el primero y $1 el segundo. La skill trabaja con el argumento equivocado sin fallar. |
| Corrección | Reindexar a base 0, o usar argumentos nombrados declarados en arguments y referenciados como $name. Recordar que si $ARGUMENTS no aparece en el cuerpo, los argumentos se anexan como ARGUMENTS: <valor>. |
| Fuente | https://code.claude.com/docs/en/skills#available-string-substitutions |
<!-- MALO -->
Revisa el archivo $1 buscando $2.
<!-- BUENO -->
Revisa el archivo $0 buscando $1.
AP-SKL-03 — description + when_to_use genéricos o demasiado largos
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Descripciones tipo «ayuda con tareas del proyecto», o la suma de description + when_to_use supera 1.536 caracteres. |
| Detección | for f in .claude/skills/*/SKILL.md; do awk '/^---$/{c++;next} c==1' "$f" | grep -E '^(description|when_to_use):' | wc -c | xargs echo "$f"; done |
| Por qué duele | description y when_to_use son lo único que está siempre en contexto y son el disparador de la skill: si son vagos, la skill no se invoca cuando corresponde. Y se truncan a 1.536 caracteres en el listado, así que lo que sobra no existe. |
| Corrección | Describir la categoría de tareas y los disparadores concretos, con lo crítico al principio, por debajo del límite. |
| Fuente | https://code.claude.com/docs/en/skills#frontmatter-reference |
AP-SKL-04 — SKILL.md monolítico sin progressive disclosure
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Un SKILL.md de varios cientos de líneas, sin reference.md, examples/ ni scripts/ en el directorio. |
| Detección | for d in .claude/skills/*/; do echo "$(wc -l < "$d/SKILL.md") $d $(ls "$d" | tr '\n' ' ')"; done | sort -rn |
| Por qué duele | El cuerpo del SKILL.md carga al invocarse y permanece en contexto en los turnos siguientes: es coste recurrente, no puntual. Los archivos de apoyo, en cambio, solo se leen cuando SKILL.md los referencia. |
| Corrección | Dividir en tres niveles: metadata (name + description) → SKILL.md compacto → archivos de apoyo referenciados. Separar contextos mutuamente excluyentes en archivos distintos. |
| Fuente | https://code.claude.com/docs/en/skills, https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills |
AP-SKL-05 — Conocimiento ocasional en CLAUDE.md en vez de skill
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Secciones de CLAUDE.md que describen un procedimiento que se ejecuta pocas veces (release, migración de datos, rotación de claves). |
| Detección | grep -nE '^#{2,3} ' CLAUDE.md 2>/dev/null y clasificar cada sección como “siempre” u “ocasional”. |
| Por qué duele | Ese texto se paga en todas las sesiones. Las skills cargan bajo demanda «without bloating every conversation». |
| Corrección | Mover a .claude/skills/<proceso>/SKILL.md. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-SKL-06 — commands/x.md y skills/x/SKILL.md duplicados
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Mismo nombre en ambos directorios. |
| Detección | comm -12 <(ls .claude/commands 2>/dev/null | sed 's/\.md$//' | sort) <(ls -d .claude/skills/*/ 2>/dev/null | xargs -n1 basename | sort) |
| Por qué duele | Ante colisión gana la skill: el archivo de commands/ es código muerto que alguien seguirá editando esperando efecto. |
| Corrección | Borrar el duplicado de commands/. |
| Fuente | https://code.claude.com/docs/en/slash-commands |
AP-SKL-07 — Skill sin disable-model-invocation que se dispara sola
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Skills con efectos irreversibles (deploy, migración, borrado) cuyo frontmatter no declara disable-model-invocation: true. |
| Detección | grep -rLn 'disable-model-invocation' .claude/skills/*/SKILL.md 2>/dev/null | xargs -r grep -lniE 'deploy|migrate|drop|delete|release' |
| Por qué duele | Sin ese campo, el modelo puede invocarla por su cuenta cuando la description le parece relevante. |
| Corrección | Añadir disable-model-invocation: true para dejarla exclusivamente manual. |
| Fuente | https://code.claude.com/docs/en/skills#frontmatter-reference |
4.2 Familia AGT — Subagentes
AP-AGT-01 — Subagente tratado como frontera de seguridad
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | Documentación interna, comentarios o diseño que justifican correr algo en un subagente “porque está aislado” o “porque tiene las herramientas recortadas”. |
| Detección | grep -rniE 'aisl|sandbox|seguro|isolat' .claude/agents/ 2>/dev/null y revisar la intención declarada. |
| Por qué duele | «Subagents run in the same process as the parent session and use the same sandbox configuration». No son un límite de seguridad: su valor es aislamiento de contexto y restricción de herramientas. Confiar en ellos como barrera es exactamente el caso de severidad CRÍTICA (control que se cree tener y no existe). |
| Corrección | Poner la barrera donde sí se aplica: permissions.deny, sandbox, o deshabilitar el subagente entero con Agent(NombreAgente) en deny. |
| Fuente | https://code.claude.com/docs/en/sandboxing |
AP-AGT-02 — description vaga
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | description de una línea que nombra un tema sin decir objetivo, formato de salida, herramientas ni límites. |
| Detección | grep -A1 '^description:' .claude/agents/*.md 2>/dev/null |
| Por qué duele | Antipatrón de delegación medido: instrucciones vagas al subagente producen trabajo duplicado, huecos y fallos. El patrón es que cada subagente reciba objetivo, formato de salida, guía sobre herramientas y fuentes, y límites claros. |
| Corrección | Reescribir con los cuatro componentes del brief (ver P-AGT-01). |
| Fuente | https://www.anthropic.com/engineering/multi-agent-research-system |
AP-AGT-03 — Multi-agente en tareas de código con contexto compartido
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Orquestaciones de varios subagentes editando el mismo módulo o dependiendo unos de otros dentro de una tarea de implementación. |
| Detección | Revisión de .claude/agents/ + los prompts de orquestación del repo. |
| Por qué duele | Economía medida: los agentes usan ~4x más tokens que un chat y los sistemas multi-agente ~15x; el uso de tokens explica el 80% de la varianza de desempeño. El antipatrón explícito es multi-agente donde todos los agentes necesitan el mismo contexto o hay fuertes interdependencias, «como la mayoría de tareas de código». Regla operativa: subir de modelo rinde más que duplicar el presupuesto de tokens. |
| Corrección | Un solo agente con más esfuerzo (/effort xhigh) o mejor modelo; reservar el fan-out para direcciones independientes cuyos requisitos de información exceden una ventana. |
| Fuente | https://www.anthropic.com/engineering/multi-agent-research-system |
AP-AGT-04 — Arquitectura que ignora los topes de anidamiento y de sesión
| Campo | Valor |
|---|
| Severidad | MEDIA · Desde v2.1.172 / v2.1.212 |
| Síntoma | Cadenas de delegación de más de cinco niveles («el subagente lanza subagentes que lanzan subagentes…»), o barridos que abren cientos de subagentes dentro de una misma sesión. |
| Detección | grep -rniE 'subagente|subagent|Agent tool|delega' .claude/agents/*.md 2>/dev/null |
| Por qué duele | Desde v2.1.172 un subagente sí puede lanzar subagentes propios (hereda la herramienta Agent), pero un subagente en profundidad cinco ya no recibe Agent y no puede anidar más: el tope es fijo y no configurable. La profundidad de un subagente en background queda fijada al lanzarlo, y reanudarlo desde un contexto más superficial no la reinicia. Aparte, desde v2.1.212 rige un tope de 200 subagentes por sesión, elevable con CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION (cualquier entero positivo, sin cota superior, pero no se puede desactivar); cuentan los anidados, los forks y los de background. Al alcanzarlo, la herramienta Agent falla con Subagent spawn limit reached y el plan de ejecución se degrada. |
| Corrección | Aplanar el árbol: que el hilo principal orqueste el fan-out en lugar de encadenar niveles. El patrón medido es líder lanzando 3-5 subagentes en paralelo (no en serie), cada uno usando 3+ herramientas en paralelo. Para impedir que un subagente concreto anide, omitir Agent de su tools o añadirlo a disallowedTools. /clear reinicia el contador de sesión. |
| Fuente | https://code.claude.com/docs/en/sub-agents#spawn-nested-subagents, https://code.claude.com/docs/en/sub-agents#session-subagent-limit |
AP-AGT-05 — Organizar por path esperando namespacing
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | .claude/agents/review/security.md y .claude/agents/infra/security.md con el mismo campo name. |
| Detección | grep -h '^name:' $(find .claude/agents ~/.claude/agents -name '*.md' 2>/dev/null) | sort | uniq -d |
| Por qué duele | Los directorios se escanean recursivamente y la identidad viene solo del campo name, no del path. Dos archivos con el mismo name colisionan. La excepción son los plugins, donde agents/review/security.md sí registra my-plugin:review:security. |
| Corrección | Nombres únicos en el campo name. Recordar la precedencia: managed settings > --agents (JSON de sesión) > .claude/agents/ > ~/.claude/agents/ > directorio agents/ de plugin. |
| Fuente | https://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope |
AP-AGT-06 — hooks, mcpServers o permissionMode en subagentes de plugin
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Un subagente distribuido dentro de un plugin declara alguno de esos tres campos. |
| Detección | grep -rnE '^(hooks|mcpServers|permissionMode):' */agents/*.md .claude-plugin/../agents/*.md 2>/dev/null |
| Por qué duele | Los subagentes de plugin ignoran por seguridad hooks, mcpServers y permissionMode. El comportamiento declarado no ocurre y no hay error. |
| Corrección | Mover esa configuración al nivel del plugin o del proyecto; documentar la limitación en el README del plugin. |
| Fuente | https://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope |
AP-AGT-07 — model: con ID fechado o alias retirado
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | model: claude-3-5-sonnet-20241022, model: claude-opus-4-8-20260501 o similares. |
| Detección | grep -rhn '^model:' .claude/agents/*.md ~/.claude/agents/*.md 2>/dev/null |
| Por qué duele | Desde la generación 4.6 los IDs no llevan sufijo de fecha (siguen siendo snapshots pinneados): añadirla produce 404. Y varios modelos están retirados: claude-3-7-sonnet-20250219 y claude-3-5-haiku-20241022 (19 feb 2026), claude-3-opus-20240229 (5 ene 2026); claude-opus-4-1-20250805 se retira el 5 ago 2026. |
| Corrección | Usar alias (sonnet, opus, haiku, fable) o IDs vigentes (claude-opus-4-8, claude-sonnet-5, claude-fable-5). El default de model en un subagente es inherit: si no hay razón para fijarlo, quitarlo. |
| Fuente | https://platform.claude.com/docs/en/about-claude/model-deprecations (fechas de retiro), https://platform.claude.com/docs/en/about-claude/models/overview, https://code.claude.com/docs/en/sub-agents#choose-a-model |
AP-AGT-08 — Devolver el volcado completo en vez de un resumen
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | El prompt del subagente pide «devolvé todo lo que encuentres», «pegá el contenido de los archivos», sin límite de salida. |
| Detección | grep -rniE 'devolvé todo|pegá el|contenido completo|full output' .claude/agents/*.md 2>/dev/null |
| Por qué duele | El valor del subagente es que trabaja en una ventana limpia y devuelve solo el resumen. Si devuelve el volcado, se pierde el ahorro de contexto y se paga el sobrecoste del fan-out sin su beneficio. |
| Corrección | Fijar en la description un resumen condensado de 1.000-2.000 tokens, que es el rango de referencia. Para trabajo voluminoso, que el subagente guarde en un archivo y devuelva la ruta. |
| Fuente | https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents |
Contraste explícito que hay que tener presente al auditar: el campo tools de un subagente SÍ es whitelist; el allowed-tools de una skill NO lo es (pre-aprueba). Esa asimetría es la que origina AP-SKL-01 y es el error de configuración más repetido entre quienes escriben skills después de haber escrito subagentes.
4.3 Familia HOOK — Hooks
Los hooks tienen 30 eventos soportados y cinco tipos de handler (command, http, mcp_tool, prompt y agent, los dos últimos evaluados por modelo/subagente, con agent experimental). Fuente transversal: https://code.claude.com/docs/en/hooks.
AP-HOOK-01 — exit 1 para bloquear
| Campo | Valor |
|---|
| Severidad | CRÍTICA (capador: CON RESERVAS) |
| Síntoma | Un script de hook de política termina con exit 1 en la rama de rechazo. |
| Detección | grep -rn 'exit 1' .claude/hooks/ ~/.claude/hooks/ 2>/dev/null |
| Por qué duele | «Claude Code treats exit code 1 as a non-blocking error and proceeds with the action». Solo exit 2 bloquea (se ignora stdout/JSON y stderr es la razón del bloqueo); cualquier otro código es error no bloqueante. El equipo cree tener un gate y la acción se ejecuta igual. Excepción documentada: en WorktreeCreate cualquier código distinto de 0 aborta la creación. |
| Corrección | Cambiar a exit 2 y escribir la razón en stderr. Desde v2.1.214, exit 2 con JSON inválido también bloquea, usando stderr como razón (antes se trataba como no bloqueante). |
| Fuente | https://code.claude.com/docs/en/hooks#exit-code-output |
# MALO: no bloquea nada
if es_peligroso "$CMD"; then
echo "comando bloqueado" >&2
exit 1
fi
# BUENO
if es_peligroso "$CMD"; then
echo "Bloqueado: el comando toca la base de producción." >&2
exit 2
fi
AP-HOOK-02 — Hooks como control de acceso duro
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | El único mecanismo que impide una acción prohibida es un hook PreToolUse con filtro if, sin regla equivalente en permissions.deny. |
| Detección | jq -r '.hooks.PreToolUse[]?.hooks[]?.if // empty' .claude/settings.json ~/.claude/settings.json 2>/dev/null y cruzar con .permissions.deny. |
| Por qué duele | «Because the if filter is best-effort, use the permission system rather than a hook to enforce a hard allow or deny». Los hooks son deterministas para efectos secundarios, no para enforcement. |
| Corrección | Duplicar la prohibición en permissions.deny y dejar el hook para logging, notificación o contexto adicional. |
| Fuente | https://code.claude.com/docs/en/hooks |
AP-HOOK-03 — Evento inexistente
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Claves como PreSubmit, ToolUse, AgentStart, OnStart dentro de .hooks. |
| Detección | jq -r '.hooks | keys[]' .claude/settings.json ~/.claude/settings.json .claude/settings.local.json 2>/dev/null y comparar contra la lista de 30. |
| Por qué duele | El hook nunca dispara y no hay error visible. Los 30 eventos reales son: SessionStart, Setup, UserPromptSubmit, UserPromptExpansion, PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure, PostToolBatch, Notification, MessageDisplay, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, Stop, StopFailure, TeammateIdle, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, PreCompact, PostCompact, Elicitation, ElicitationResult y SessionEnd. |
| Corrección | Renombrar al evento real que corresponde a la intención. |
| Fuente | https://code.claude.com/docs/en/hooks |
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Un hook PreToolUse emite {"decision": "approve"} o {"decision": "block"} en la raíz del JSON. |
| Detección | grep -rn '"decision"' .claude/hooks/ ~/.claude/hooks/ .claude/settings.json 2>/dev/null |
| Por qué duele | En PreToolUse la decisión va en hookSpecificOutput.permissionDecision con valores allow | deny | ask | defer, más permissionDecisionReason, updatedInput y additionalContext. El campo top-level decision: "block" corresponde a PostToolUse, que además no puede bloquear porque la herramienta ya se ejecutó (igual que PostToolUseFailure y PermissionDenied). |
| Corrección | Migrar al esquema correcto, incluyendo hookEventName que es obligatorio dentro de hookSpecificOutput. |
| Fuente | https://code.claude.com/docs/en/hooks#pretooluse |
// MALO
{ "decision": "block", "reason": "falta el changelog" }
// BUENO
{ "hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Falta la entrada de changelog para este cambio." } }
AP-HOOK-05 — Hook Stop diseñado como gate infinito
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Un hook Stop que bloquea el fin del turno mientras no se cumpla una condición, sin salida alternativa. |
| Detección | jq -r '.hooks.Stop' .claude/settings.json ~/.claude/settings.json 2>/dev/null |
| Por qué duele | Claude Code anula el hook Stop tras 8 bloqueos consecutivos. Diseñar un gate asumiendo bloqueo indefinido es un antipatrón explícito: a partir del noveno intento el turno termina igual. |
| Corrección | Usarlo como uno de los cuatro niveles de forzado (§5, P-VER-01), no como el único, y hacer que el mensaje de bloqueo indique la acción concreta que destraba. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-HOOK-06 — Script de hook sin higiene de shell
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Variables sin comillas, rutas relativas, ausencia de ${CLAUDE_PROJECT_DIR}, sin bloqueo de .., o acceso a .env, .git/ y llaves. |
| Detección | grep -rnE '\$[A-Za-z_]+[^"]' .claude/hooks/*.sh 2>/dev/null; grep -rn '\.env|\.git/|id_rsa' .claude/hooks/ 2>/dev/null |
| Por qué duele | «Command hooks execute shell commands with your full user permissions… Review and test all hook commands». Una variable sin comillas con contenido controlado por el modelo (que a su vez puede estar influido por contenido externo) es ejecución arbitraria con los permisos del usuario. |
| Corrección | Entrecomillar todas las variables ("$VAR"), usar rutas absolutas con ${CLAUDE_PROJECT_DIR}, validar y sanitizar entradas, rechazar path traversal (..) y evitar archivos sensibles. |
| Fuente | https://code.claude.com/docs/en/hooks |
# MALO
cd $CLAUDE_PROJECT_DIR/scripts && ./validar.sh $FILE
# BUENO
set -euo pipefail
case "$FILE" in *..*) echo "path traversal" >&2; exit 2;; esac
"${CLAUDE_PROJECT_DIR}/scripts/validar.sh" "$FILE"
AP-HOOK-07 — Matcher mcp__<server>__ contra un servidor de plugin
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Matcher tipo mcp__database-tools__.* cuando ese servidor viene de un plugin. |
| Detección | grep -rn 'mcp__' .claude/settings.json ~/.claude/settings.json 2>/dev/null | grep -v 'mcp__plugin_' cruzado con la lista de plugins instalados. |
| Por qué duele | Las herramientas de plugins se llaman mcp__plugin_<plugin>_<server>__<tool> y el servidor se registra como plugin:<plugin>:<server>. Un matcher contra la clave desnuda nunca dispara. |
| Corrección | Escribir el matcher con el prefijo completo del plugin. |
| Fuente | https://code.claude.com/docs/en/mcp |
AP-HOOK-08 — Trabajo pesado síncrono
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Hooks que corren suites de tests, builds o llamadas de red lentas sin async: true. |
| Detección | grep -rn 'command' .claude/settings.json | grep -iE 'test|build|npm run|pytest|cargo' y comprobar la ausencia de "async": true. |
| Por qué duele | Un hook lento congela el turno hasta su timeout. Los defaults son 600 s para hooks de comando/HTTP/mcp_tool, 30 s para UserPromptSubmit, 10 s para MessageDisplay, 30 s para prompt hooks y 60 s para agent hooks. |
| Corrección | Marcar async: true (con asyncRewake si hace falta reanudar) o mover el trabajo pesado a CI. |
| Fuente | https://code.claude.com/docs/en/hooks |
AP-HOOK-09 — Esperar que stdout inyecte contexto donde no lo hace
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Un hook PostToolUse, Notification o SessionEnd imprime texto por stdout esperando que Claude lo lea. |
| Detección | jq -r '.hooks | to_entries[] | select(.key | IN("UserPromptSubmit","UserPromptExpansion","SessionStart") | not) | .key' .claude/settings.json 2>/dev/null y revisar esos scripts. |
| Por qué duele | Con exit 0 se parsea stdout como JSON, pero solo UserPromptSubmit, UserPromptExpansion y SessionStart inyectan stdout como contexto visible. En el resto, el texto se pierde. |
| Corrección | Usar hookSpecificOutput.additionalContext donde el evento lo soporte, systemMessage para avisar al usuario, o mover la inyección a uno de los tres eventos que sí la hacen. |
| Fuente | https://code.claude.com/docs/en/hooks#json-output |
AP-HOOK-10 — Salidas de más de 10.000 caracteres
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Hooks que emiten diffs completos, logs de build o listados largos. |
| Detección | Ejecutar el hook con una entrada representativa y medir: bash .claude/hooks/x.sh < fixture.json | wc -c |
| Por qué duele | Las cadenas del JSON de salida se truncan a 10.000 caracteres: el final del mensaje —donde suele estar la conclusión— desaparece. |
| Corrección | Resumir en el script y, si hace falta el detalle, escribirlo a un archivo y devolver la ruta. |
| Fuente | https://code.claude.com/docs/en/hooks#json-output |
4.4 Familia MCP — Servidores MCP
AP-MCP-01 — Entry con url sin type
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Un servidor en .mcp.json o ~/.claude.json declara url y no declara type. |
| Detección | jq -r '.mcpServers | to_entries[] | select(.value.url and (.value.type | not)) | .key' .mcp.json ~/.claude.json 2>/dev/null |
| Por qué duele | stdio es el default cuando no se declara type, así que un entry con url es error de configuración: el servidor se omite y se reporta MCP server "<name>" has a "url" but no "type" (antes de v2.1.202 el error era el confuso command: expected string, received undefined). |
| Corrección | Declarar "type": "http" (o su alias streamable-http). |
| Fuente | https://code.claude.com/docs/en/mcp |
// MALO
{ "mcpServers": { "docs": { "url": "https://mcp.example.com" } } }
// BUENO
{ "mcpServers": { "docs": { "type": "http", "url": "https://mcp.example.com" } } }
AP-MCP-02 — Transporte sse
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | "type": "sse" en algún servidor. |
| Detección | jq -r '.mcpServers | to_entries[] | select(.value.type=="sse") | .key' .mcp.json ~/.claude.json 2>/dev/null |
| Por qué duele | sse está deprecado. Los cuatro transportes son stdio, http (Streamable HTTP, recomendado para remotos), sse (deprecado) y ws (WebSocket, solo configurable por .mcp.json o claude mcp add-json, no por --transport). |
| Corrección | Migrar a http si el servidor lo soporta. |
| Fuente | https://code.claude.com/docs/en/mcp |
AP-MCP-03 — Podar servidores “para ahorrar contexto”
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Comentarios o commits que eliminan servidores MCP citando consumo de tokens de arranque. |
| Detección | git log --oneline -- .mcp.json | head -20 y revisar los mensajes. |
| Por qué duele | Con tool search activo por defecto las definiciones se difieren y solo se cargan bajo demanda vía la herramienta ToolSearch; al inicio entran solo nombres de herramientas e instrucciones del servidor. No hay tope fijo de herramientas por servidor. El riesgo residual real es de superficie de ataque y permisos, no de contexto. |
| Corrección | Decidir la poda por confianza y permisos. Si se necesita el comportamiento antiguo, ENABLE_TOOL_SEARCH=false carga todo upfront; auto carga upfront si las definiciones caben en el 10% de la ventana y difiere el resto; auto:N fija ese umbral. |
| Fuente | https://code.claude.com/docs/en/mcp |
AP-MCP-04 — alwaysLoad: true abusado
| Campo | Valor |
|---|
| Severidad | MEDIA · Desde v2.1.121 |
| Síntoma | Varios servidores con "alwaysLoad": true. |
| Detección | jq -r '.mcpServers | to_entries[] | select(.value.alwaysLoad==true) | .key' .mcp.json ~/.claude.json 2>/dev/null |
| Por qué duele | alwaysLoad exime del tool search pero bloquea el arranque hasta conectar, con tope de 5 s por servidor. Con varios servidores lentos, cada sesión arranca con segundos de espera. |
| Corrección | Dejarlo solo donde el servidor es imprescindible desde el primer turno. Alternativa de grano fino: "anthropic/alwaysLoad": true en el _meta de una herramienta concreta. |
| Fuente | https://code.claude.com/docs/en/mcp |
AP-MCP-05 — Secretos en .mcp.json
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | Tokens en headers o en env dentro de .mcp.json (versionado). |
| Detección | jq -r '.mcpServers | to_entries[] | select(.value.headers or .value.env) | .key + ": " + ((.value.headers // .value.env) | keys | join(","))' .mcp.json 2>/dev/null |
| Por qué duele | Queda en el repositorio y en cada clone. Se reporta la ruta y el nombre de la cabecera, con el valor como [REDACTADO]. |
| Corrección | Usar headersHelper: un comando que escribe un objeto JSON de headers en stdout, con timeout de 10 s, ejecutado en cada conexión (sin caché), que recibe CLAUDE_CODE_MCP_SERVER_NAME, CLAUDE_CODE_MCP_SERVER_URL y CLAUDE_PLUGIN_ROOT, y que desde v2.1.193 se re-ejecuta y reintenta una vez ante 401/403. O bien OAuth: claude mcp login <name> (v2.1.186+) guarda el secreto en el keychain del sistema, no en el config. |
| Fuente | https://code.claude.com/docs/en/mcp |
// MALO
{ "mcpServers": { "api": { "type": "http", "url": "https://api.example.com/mcp",
"headers": { "Authorization": "Bearer sk-live-..." } } } }
// BUENO
{ "mcpServers": { "api": { "type": "http", "url": "https://api.example.com/mcp",
"headersHelper": "./scripts/mcp-headers.sh" } } }
AP-MCP-06 — allowedMcpServers filtrando por serverName
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | Política de organización que restringe servidores usando el campo serverName. |
| Detección | jq -r '.allowedMcpServers' /etc/claude-code/managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json" 2>/dev/null |
| Por qué duele | serverName es un match literal sobre un nombre que elige el usuario: la doc dice explícitamente que no es control de seguridad. Basta renombrar un servidor para pasar el filtro. |
| Corrección | Filtrar por serverUrl (admite wildcards *) o serverCommand (match exacto de todos los args, en orden). El denylist siempre gana y siempre fusiona todas las fuentes; allowManagedMcpServersOnly: true limita el allowlist a fuentes gestionadas; allowedMcpServers sin definir permite todo y [] bloquea todo. Para control exclusivo, managed-mcp.json. |
| Fuente | https://code.claude.com/docs/en/managed-mcp |
AP-MCP-07 — Payloads sin paginado
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Servidores propios cuyas herramientas devuelven colecciones completas sin parámetros de límite, filtro o rango. |
| Detección | Ejecutar una llamada representativa y observar el aviso de salida grande, o revisar el esquema de las tools del servidor propio. |
| Por qué duele | Hay aviso a los 10.000 tokens (umbral fijo, no configurable) y límite máximo por defecto de 25.000 tokens, ajustable con MAX_MCP_OUTPUT_TOKENS. Un servidor puede subir el umbral de persistencia a disco con _meta["anthropic/maxResultSizeChars"], con techo duro de 500.000 caracteres y solo para texto. |
| Corrección | Añadir paginación, filtrado, selección de rango y truncado con defaults sensatos; ofrecer un parámetro enum de formato tipo DETAILED/CONCISE (CONCISE consume del orden de un tercio); devolver identificadores semánticos en vez de UUIDs crípticos. |
| Fuente | https://code.claude.com/docs/en/mcp, https://www.anthropic.com/engineering/writing-tools-for-agents |
AP-MCP-08 — Scope mal elegido
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | El mismo nombre de servidor definido en varios scopes con configuraciones distintas. |
| Detección | jq -r '.mcpServers | keys[]' .mcp.json 2>/dev/null | sort > /tmp/p.txt; jq -r '.mcpServers | keys[]' ~/.claude.json 2>/dev/null | sort | comm -12 - /tmp/p.txt |
| Por qué duele | La precedencia es local > project > user > plugin > conectores de claude.ai y se usa la definición completa de la fuente ganadora, sin merge de campos: un local viejo anula por completo el project actualizado. Los tres scopes hacen match por nombre; plugins y conectores, por endpoint. |
| Corrección | Definir cada servidor en un solo scope: project (.mcp.json) para lo compartido, user para lo personal transversal, local solo para experimentos. |
| Fuente | https://code.claude.com/docs/en/mcp |
AP-MCP-09 — Servidores que traen contenido externo sin evaluar prompt injection
| Campo | Valor |
|---|
| Severidad | CRÍTICA |
| Síntoma | Servidores que leen issues, correos, páginas web o tickets, combinados con permisos amplios de escritura o de Bash. |
| Detección | jq -r '.mcpServers | keys[]' .mcp.json ~/.claude.json 2>/dev/null y clasificar cuáles ingieren contenido de terceros. |
| Por qué duele | Modelo de amenaza oficial: «their behavior can be influenced by the content they process: files, webpages, or user input. This is sometimes called prompt injection. For example, if a repository’s README contains unusual instructions, Claude Code might incorporate those into its actions in ways the operator didn’t anticipate». Y sobre MCP: «Verify you trust each server before connecting it. Servers that fetch external content can expose you to prompt injection risk». Anthropic revisa conectores del Directory contra criterios de listado pero no audita ni gestiona servidores MCP. |
| Corrección | Defensa en profundidad: sandbox con failIfUnavailable, permissions.deny sobre acciones destructivas, requiresUserInteraction en operaciones irreversibles, y no combinar ingestión externa con allowlists amplias de Bash. Tener presente que WebFetch usa una ventana de contexto aislada y que los resultados de búsqueda web se resumen en vez de pasar crudos, pero eso no cubre a los servidores MCP de terceros. |
| Fuente | https://code.claude.com/docs/en/agent-sdk/secure-deployment, https://code.claude.com/docs/en/mcp, https://code.claude.com/docs/en/security |
4.5 Familia MOD — Modelos, CLI y headless
AP-MOD-01 — ID de modelo con sufijo de fecha en generación ≥ 4.6
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Cadenas como claude-opus-4-8-20260501, claude-sonnet-5-20260615 en settings, subagentes, código o CI. |
| Detección | grep -rnE 'claude-(opus|sonnet|fable|haiku)-[0-9-]+-20[0-9]{6}' . --include='*.json' --include='*.md' --include='*.ts' --include='*.py' --include='*.yml' 2>/dev/null | grep -v node_modules |
| Por qué duele | Desde la generación 4.6 los IDs no llevan sufijo de fecha (y siguen siendo snapshots pinneados, no punteros evergreen). Con fecha, la API devuelve 404. |
| Corrección | Usar claude-opus-4-8, claude-sonnet-5, claude-fable-5. Excepción vigente: claude-haiku-4-5-20251001 y los legacy claude-sonnet-4-5-20250929 y claude-opus-4-5-20251101 sí llevan pin fechado. |
| Fuente | https://platform.claude.com/docs/en/about-claude/models/overview |
AP-MOD-02 — thinking.budget_tokens en modelos que lo eliminaron
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Código o configuración con thinking: {type: "enabled", budget_tokens: N} apuntando a Fable 5, Opus 4.8, Opus 4.7 o Sonnet 5. |
| Detección | grep -rn 'budget_tokens' . --include='*.ts' --include='*.js' --include='*.py' --include='*.json' 2>/dev/null | grep -v node_modules |
| Por qué duele | Está eliminado en esos modelos: devuelve 400. Sigue funcionando, deprecado, en Opus 4.6 y Sonnet 4.6, y es soportado en Haiku 4.5 (único de la tabla vigente que mantiene extended thinking clásico y que no soporta adaptive thinking). |
| Corrección | Sustituir por thinking: {type: "adaptive"}. Notas sobre cómo apagar el thinking: en Fable 5 y Mythos está siempre activo y {type:"disabled"} da 400; en Opus 4.8/4.7 la forma documentada de correr sin thinking es omitir el campo thinking (con {type:"adaptive"} explícito se activa), y {type:"disabled"} también devuelve 400. El único modelo vigente donde la doc indica explícitamente pasar thinking: {type:"disabled"} para apagarlo es Sonnet 5, que corre con adaptive por defecto. Si se necesita ver el razonamiento, thinking: {type:"adaptive", display:"summarized"}, porque el default de thinking.display es "omitted" y los bloques llegan vacíos. |
| Fuente | https://platform.claude.com/docs/en/build-with-claude/effort · https://platform.claude.com/docs/en/about-claude/models/migration-guide |
AP-MOD-03 — temperature/top_p/top_k o prefill del turno assistant final
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Llamadas con parámetros de sampling, o con un turno assistant final prellenado para forzar formato. |
| Detección | grep -rnE 'temperature|top_p|top_k' . --include='*.ts' --include='*.py' 2>/dev/null | grep -v node_modules |
| Por qué duele | temperature, top_p y top_k devuelven 400 cuando se fijan a cualquier valor no-default —la misma regla, por igual, en Fable 5, Mythos 5, Opus 4.8, Opus 4.7 y Sonnet 5; omitir el parámetro (o pasar su valor por defecto) sigue siendo válido. El prefill del turno assistant final también devuelve 400 en Fable 5, Mythos 5, Opus 4.6/4.7/4.8 y Sonnet 4.6/5. |
| Corrección | Dirigir el comportamiento por prompting y usar structured outputs: output_config.format = {"type":"json_schema","schema":{…}} (GA; output_format y la cabecera beta structured-outputs-2025-11-13 solo funcionan durante un periodo de transición). |
| Fuente | https://platform.claude.com/docs/en/about-claude/models/overview, https://platform.claude.com/docs/en/build-with-claude/structured-outputs |
AP-MOD-04 — effort top-level en vez de output_config.effort
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Payloads con "effort": "high" en la raíz, o con la cabecera beta effort-2025-11-24. |
| Detección | grep -rn '"effort"|effort-2025-11-24' . --include='*.ts' --include='*.py' --include='*.json' 2>/dev/null | grep -v node_modules |
| Por qué duele | El esfuerzo es GA sin cabecera beta y va anidado en output_config: output_config: {effort: "low"|"medium"|"high"|"xhigh"|"max"}. Fuera de ahí no tiene efecto. |
| Corrección | Mover a output_config.effort. El default es high en Opus 4.8 y Sonnet 5; xhigh es el nivel recomendado para coding y agentes. Opus 4.6 y Sonnet 4.6 no tienen xhigh. En Claude Code el menú /effort incluye además ultracode, que envía xhigh y hace que Claude orqueste dynamic workflows, solo para la sesión actual. |
| Fuente | https://platform.claude.com/docs/en/about-claude/models/overview, https://code.claude.com/docs/en/model-config |
AP-MOD-05 — -p en CI sin --bare y sin saber que la verificación de trust queda desactivada
| Campo | Valor |
|---|
| Severidad | CRÍTICA si el pipeline procesa contenido externo (capador: REPROBADO junto a AP-PERM-14); ALTA en el resto |
| Síntoma | Workflows de CI con claude -p sin --bare, especialmente si el prompt incluye issues, PRs de terceros, comentarios o payloads de webhook. |
| Detección | grep -rn 'claude -p|claude --print' .github/ .gitlab-ci.yml Makefile scripts/ 2>/dev/null | grep -v -- '--bare' |
| Por qué duele | La verificación de trust está desactivada al correr no interactivamente con -p: «Trust verification is disabled when running non-interactively with the -p flag». Sin --bare, además, «claude -p loads the same context an interactive session would, including anything configured in the working directory or ~/.claude»: hooks, skills, plugins, MCP, auto memory y CLAUDE.md del repo que está procesando, incluidos los de un fork no confiable. |
| Corrección | Añadir --bare (omite hooks, skills, plugins, MCP, auto memory y CLAUDE.md; será el default de -p en el futuro) y pasar explícitamente el --allowedTools mínimo. Recordar los límites operativos: stdin por pipe limitado a 10MB desde v2.1.128 y SIGTERM sale con código 143. En auto mode con -p, los bloqueos repetidos abortan la sesión porque no hay usuario a quien preguntar. |
| Fuente | https://code.claude.com/docs/en/headless, https://code.claude.com/docs/en/security |
# MALO
- run: claude -p "Resumí el issue #${{ github.event.issue.number }} y aplica el fix"
# BUENO
- run: claude -p --bare --output-format json --allowedTools "Read,Grep" "$PROMPT"
AP-MOD-06 — CLAUDE_CODE_ENABLE_AUTO_MODE=1 residual
| Campo | Valor |
|---|
| Severidad | BAJA · Sin efecto desde v2.1.207 |
| Síntoma | La variable exportada en dotfiles, Dockerfiles o workflows. |
| Detección | grep -rn 'CLAUDE_CODE_ENABLE_AUTO_MODE' . ~/.zshrc ~/.bashrc 2>/dev/null | grep -v node_modules |
| Por qué duele | Entre v2.1.158 y v2.1.206 fue obligatoria solo en Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry y sesiones con login del Claude apps gateway — nunca en la Anthropic API ni en Claude Platform on AWS, donde auto mode estuvo disponible por defecto. En esos proveedores, sin la variable, auto mode quedaba apagado y defaultMode: "auto" se ignoraba. Desde v2.1.207 no tiene efecto (se acepta por compatibilidad). Su presencia sugiere una config congelada en el tiempo y puede llevar a creer que auto mode depende de ella. |
| Corrección | Eliminarla. Auto mode está generalizado en todos los planes y superficies. |
| Fuente | https://code.claude.com/docs/en/permission-modes |
AP-MOD-07 — Instalación sin auto-actualización
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Instalación vía Homebrew, WinGet o repos apt/dnf/apk (sin CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1 donde aplica), con una versión notablemente inferior a v2.1.217. |
| Detección | claude --version; which claude; claude doctor |
| Por qué duele | Las instalaciones nativas —y también las de npm, que instalan el mismo binario— se auto-actualizan en segundo plano. En cambio, «Homebrew, WinGet, and Linux package manager installations require manual updates by default». Para Homebrew y WinGet se puede delegar el upgrade a Claude Code con CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1; apt, dnf y apk siguen exigiendo upgrade manual porque esos comandos necesitan privilegios elevados. Una instalación vieja significa que decenas de fichas de este catálogo describen comportamientos que la máquina no tiene, y viceversa. |
| Corrección | Migrar al instalador nativo (curl -fsSL https://claude.ai/install.sh | bash, irm https://claude.ai/install.ps1 | iex, o install.cmd), o mantener el upgrade explícito del gestor: brew upgrade claude-code / brew upgrade claude-code@latest, winget upgrade Anthropic.ClaudeCode, sudo apt update && sudo apt upgrade claude-code, sudo dnf upgrade claude-code, apk update && apk upgrade claude-code. En Homebrew/WinGet, opcionalmente CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1. |
| Fuente | https://code.claude.com/docs/en/setup#update-claude-code, https://code.claude.com/docs/en/setup#auto-updates, https://code.claude.com/docs/en/env-vars |
AP-MOD-08 — total_cost_usd leído como facturación
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Dashboards, alertas o reportes que tratan total_cost_usd / costUSD como dato autoritativo. |
| Detección | grep -rn 'total_cost_usd|costUSD' . --include='*.ts' --include='*.py' --include='*.sh' 2>/dev/null | grep -v node_modules |
| Por qué duele | Son estimaciones cliente calculadas con una tabla de precios embebida en el build, no datos de facturación. Con subagentes, además, usage solo cuenta el bucle de nivel superior: hay que usar modelUsage/model_usage del mensaje result, y deduplicar por message.id porque las llamadas paralelas producen varios mensajes assistant con el mismo ID y usage idéntico. |
| Corrección | Contrastar contra la facturación real de la plataforma y documentar la estimación como tal. |
| Fuente | https://code.claude.com/docs/en/agent-sdk/cost-tracking |
4.6 Familia CTX — Operación y contexto
Restricción declarada que sostiene toda esta familia: «Claude’s context window fills up fast, and performance degrades as it fills»; el contexto es «the most important resource to manage» (https://code.claude.com/docs/en/best-practices). Estos cinco primeros son los patrones de falla nombrados en la doc.
AP-CTX-01 — Kitchen sink session
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Sesiones que mezclan tareas no relacionadas (arreglar un bug, escribir un README, revisar dependencias). |
| Detección | Revisión del transcript o de ~/.claude/projects/<project>/ para ver la duración y el salto temático de las sesiones. |
| Por qué duele | Cada tarea deja residuo en el contexto y degrada las siguientes. |
| Corrección | /clear entre tareas no relacionadas. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-CTX-02 — Correcting over and over
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Tres o más correcciones sobre el mismo punto dentro de una sesión. |
| Detección | Revisión del transcript. |
| Por qué duele | El contexto queda contaminado con enfoques fallidos. «A clean session with a better prompt almost always outperforms a long session with accumulated corrections.» |
| Corrección | Regla de las dos correcciones: a la tercera, /clear y reescribir el prompt inicial incorporando lo aprendido. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-CTX-03 — Over-specified CLAUDE.md
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Reglas de proceso en CLAUDE.md que se repiten y se siguen incumpliendo. |
| Detección | grep -nE 'siempre|nunca|antes de|después de' CLAUDE.md 2>/dev/null | wc -l |
| Por qué duele | Es el mismo diagnóstico de AP-MEM-01: cuanto más largo el archivo, más se pierde la regla. |
| Corrección | Convertir la regla en hook (si debe ocurrir sin excepción) o en skill (si es ocasional), y podar el texto. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-CTX-04 — Trust-then-verify gap
| Campo | Valor |
|---|
| Severidad | ALTA |
| Síntoma | Flujos que aceptan aserciones de éxito («listo», «los tests pasan») sin evidencia. |
| Detección | Ausencia de comandos de verificación en los prompts, en /goal, en hooks Stop y en CI. Ver P-VER-01. |
| Por qué duele | Sin un check, «looks done» es la única señal y el humano se convierte en el loop de verificación. «If you can’t verify it, don’t ship it.» |
| Corrección | Pedir evidencia: salida de tests, comando y resultado, screenshot. Ver los cuatro niveles de forzado en P-VER-01. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-CTX-05 — Infinite exploration
| Campo | Valor |
|---|
| Severidad | MEDIA |
| Síntoma | Prompts de investigación sin acotar («entiende el proyecto», «revisa todo el código»). |
| Detección | Revisión del transcript: cientos de archivos leídos antes del primer cambio. |
| Por qué duele | Investigar sin acotar llena el contexto con cientos de archivos y no queda presupuesto para el trabajo. |
| Corrección | Acotar el alcance («lee solo src/auth/») o delegar en un subagente que devuelva un resumen de 1.000-2.000 tokens. |
| Fuente | https://code.claude.com/docs/en/best-practices |
AP-CTX-06 — Plan mode para diffs de una frase
| Campo | Valor |
|---|
| Severidad | BAJA |
| Síntoma | Plan mode forzado por defecto (permissions.defaultMode: "plan") para todo tipo de cambio. |
| Detección | jq -r '.permissions.defaultMode' ~/.claude/settings.json .claude/settings.json 2>/dev/null |
| Por qué duele | Plan mode tiene coste: para typos, añadir un log o renombrar variables se pide directo. «If you could describe the diff in one sentence, skip the plan.» |
| Corrección | Activarlo por sesión con Shift+Tab, --permission-mode plan o prefijando un único prompt con /plan. Recordar que Ctrl+G abre el plan en el editor y que aceptar un plan renombra la sesión. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/permission-modes |
§ 5 — Catálogo de patrones a adoptar
Las fichas P-* usan el mismo formato de siete campos, pero el síntoma detectable es la ausencia. Cada una lleva una condición de aplicabilidad explícita: si el repositorio no cumple esa condición, el veredicto es NO_APLICA, no HALLAZGO. Esto evita reportar la falta de algo que el proyecto no necesita, en línea con la regla 8 de §1.1.
P-VER-01 — Verificación ejecutable y sus cuatro niveles de forzado
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA (capador: CON RESERVAS) |
| Aplicabilidad | Todo repositorio con código ejecutable. |
| Síntoma (ausencia) | No hay tests, ni build con exit code, ni linter, ni script contra fixture invocable por el agente; y CLAUDE.md no documenta cómo verificar. |
| Detección | grep -niE 'test|lint|build|typecheck' CLAUDE.md 2>/dev/null; jq -r '.scripts | keys[]' package.json 2>/dev/null |
| Por qué importa | Sin un check, «looks done» es la única señal. «If you can’t verify it, don’t ship it.» |
| Adopción | Documentar el comando de verificación en CLAUDE.md y escalar por niveles según criticidad. |
| Fuente | https://code.claude.com/docs/en/best-practices |
flowchart LR
N1[Nivel 1<br/>Pedir el check<br/>en el mismo prompt] --> N2[Nivel 2<br/>Condición de /goal<br/>revalidada por un<br/>evaluador separado]
N2 --> N3[Nivel 3<br/>Hook Stop que bloquea<br/>el fin del turno<br/>· se anula tras 8 bloqueos ·]
N3 --> N4[Nivel 4<br/>Subagente verificador<br/>que intenta refutar<br/>el resultado]
El nivel 3 tiene un límite documentado: Claude Code anula el hook Stop tras 8 bloqueos consecutivos (AP-HOOK-05), así que no puede ser el único nivel en un flujo autónomo.
P-VER-02 — Revisión adversarial del diff en contexto fresco
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Repos donde el agente produce diffs que van a producción. |
| Síntoma (ausencia) | Ningún paso de revisión del diff antes de dar por terminada la tarea. |
| Detección | Buscar un subagente o skill de revisión: ls .claude/agents/ .claude/skills/ 2>/dev/null | grep -iE 'review|revis' |
| Por qué importa | Un contexto fresco no está sesgado hacia el código que acaba de escribir. Existe la skill incluida /code-review. |
| Adopción | Lanzar un subagente que revise el diff contra el plan, acotado a corrección y requisitos declarados: «A reviewer prompted to find gaps will usually report some, even when the work is sound… Chasing every finding leads to over-engineering». |
| Fuente | https://code.claude.com/docs/en/best-practices |
P-VER-03 — Writer/Reviewer en dos sesiones
| Campo | Valor |
|---|
| Severidad de la ausencia | BAJA |
| Aplicabilidad | Trabajo autónomo de larga duración. |
| Síntoma (ausencia) | El mismo hilo escribe y evalúa su propio trabajo. |
| Detección | Revisión del flujo documentado en CLAUDE.md o en los scripts de automatización. |
| Por qué importa | «Afinar un evaluador independiente para que sea escéptico resulta mucho más tratable que hacer que un generador critique su propio trabajo.» El artículo describe la auto-evaluación como un problema conocido: al pedirles evaluar su propio trabajo, los agentes «tienden a elogiarlo con confianza, incluso cuando la calidad es obviamente mediocre». |
| Adopción | Dos sesiones separadas (o --worktree), una escribe y otra revisa. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://www.anthropic.com/engineering/harness-design-long-running-apps |
P-SEC-01 — El trío vigente: auto mode + allowlist + sandbox
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | Cualquier flujo con autonomía (auto mode, dontAsk, headless). |
| Síntoma (ausencia) | Se busca autonomía saltando permisos en vez de con estos tres mecanismos. |
| Detección | jq -r '{defaultMode: .permissions.defaultMode, allow: (.permissions.allow | length), sandbox: .sandbox.enabled}' ~/.claude/settings.json 2>/dev/null |
| Por qué importa | La guía ya no recomienda «safe YOLO mode» con --dangerously-skip-permissions. Los tres mecanismos actuales son auto mode, allowlists vía /permissions y sandboxing OS-level vía /sandbox. Ejemplo canónico: claude --permission-mode auto -p "fix all lint errors". |
| Adopción | Activar los tres. Tener presente el funcionamiento de auto mode: usa un modelo clasificador aparte que revisa cada acción antes de ejecutarla, corre por defecto en Claude Sonnet 5 (desde v2.1.210), sus llamadas cuentan para el consumo de tokens, y tiene frenos no configurables: 3 bloqueos seguidos o 20 en total pausan el modo y devuelven los prompts (con -p, abortan la sesión). El clasificador bloquea por defecto «lanzar un loop de agente autónomo sin aprobación humana ni sandbox» y desde v2.1.198 cubre también runners de terceros con aislamiento desactivado. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/permission-modes |
P-SEC-02 — Sandbox que falla cerrado y con credenciales protegidas
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | macOS, Linux o WSL2 (AP-SBX-05). |
| Síntoma (ausencia) | sandbox.enabled: true sin failIfUnavailable ni credentials. |
| Detección | jq -r '.sandbox | {enabled, failIfUnavailable, credentials: (.credentials != null)}' ~/.claude/settings.json 2>/dev/null |
| Por qué importa | Es la contracara positiva de AP-SBX-01 y AP-SBX-04. |
| Adopción | failIfUnavailable: true + credentials con mode: "mask" para las variables que deben llegar solo a hosts concretos (requiere network.tlsTerminate, declarado como objeto —{} o con caCertPath/caKeyPath— y se ignora desde settings de proyecto o local, así que va en user o managed). |
| Fuente | https://code.claude.com/docs/en/sandboxing |
P-SEC-03 — Endurecimiento organizacional en managed settings
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Organizaciones con despliegue gestionado (MDM, imagen corporativa). |
| Síntoma (ausencia) | No hay managed settings, o existen pero sin las claves de endurecimiento. |
| Detección | jq -r 'keys[]' /etc/claude-code/managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json" 2>/dev/null |
| Por qué importa | Los managed settings no se sobrescriben ni con flags de CLI. |
| Adopción | permissions.disableBypassPermissionsMode: "disable", permissions.disableAutoMode: "disable" si corresponde, allowManagedHooksOnly: true (bloquea hooks de usuario/proyecto/plugin salvo plugins force-enabled), strictPluginOnlyCustomization (bloquea skills, agents, hooks y servidores MCP de fuentes de usuario y proyecto) y managed-mcp.json para control exclusivo de MCP ({"mcpServers": {}} desactiva MCP por completo). |
| Fuente | https://code.claude.com/docs/en/settings, https://code.claude.com/docs/en/permissions, https://code.claude.com/docs/en/managed-mcp |
P-MEM-01 — .claude/rules/*.md con paths:
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Repos con áreas heterogéneas (monorepos, infra + app, front + back). |
| Síntoma (ausencia) | Todo el conocimiento vive en un CLAUDE.md único. |
| Detección | ls .claude/rules/*.md 2>/dev/null || echo "AUSENTE" |
| Por qué importa | Es «la alternativa recomendada a un CLAUDE.md gigante»: markdown modular que carga solo al tocar archivos que matcheen los globs. |
| Adopción | Un archivo por área con frontmatter paths:. Las reglas sin paths cargan al inicio con la misma prioridad que .claude/CLAUDE.md. |
| Fuente | https://code.claude.com/docs/en/memory |
P-MEM-02 — Poda periódica de la memoria
| Campo | Valor |
|---|
| Severidad de la ausencia | BAJA |
| Aplicabilidad | Todo repo con CLAUDE.md que haya crecido con el tiempo. |
| Síntoma (ausencia) | El archivo solo crece: git log muestra adiciones y ninguna eliminación. |
| Detección | git log --numstat --oneline -- CLAUDE.md | awk 'NF==3 {add+=$1; del+=$2} END {print "añadidas:", add, "borradas:", del}' |
| Por qué importa | «Tratar CLAUDE.md como código: revisar, podar y verificar que el comportamiento cambia.» |
| Adopción | Pasar cada línea por el criterio «¿quitar esta línea haría que Claude se equivoque?». |
| Fuente | https://code.claude.com/docs/en/best-practices |
P-SKL-01 — Progressive disclosure de tres niveles y scripts deterministas
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Repos con al menos una skill. |
| Síntoma (ausencia) | Skills de un solo archivo largo, sin reference.md ni scripts/. |
| Detección | ls .claude/skills/*/ 2>/dev/null |
| Por qué importa | Los tres niveles son: (1) metadata (name + description) en el system prompt al inicio; (2) el SKILL.md completo cuando Claude lo juzga relevante; (3+) archivos adicionales solo si hacen falta. |
| Adopción | Dividir el SKILL.md cuando crece, separar contextos mutuamente excluyentes e incluir scripts pre-escritos para lo que conviene resolver con código determinista en vez de generación de tokens. |
| Fuente | https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills |
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | Skills que deben ser de solo lectura o de alcance acotado. |
| Síntoma (ausencia) | Skills de análisis o auditoría sin disallowed-tools. |
| Detección | grep -rL 'disallowed-tools' .claude/skills/*/SKILL.md 2>/dev/null |
| Por qué importa | Es la contracara de AP-SKL-01: allowed-tools pre-aprueba, disallowed-tools restringe. |
| Adopción | disallowed-tools: Edit, Write, NotebookEdit en toda skill de análisis. |
| Fuente | https://code.claude.com/docs/en/skills#frontmatter-reference |
P-AGT-01 — Brief de delegación completo
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | Repos con subagentes definidos. |
| Síntoma (ausencia) | description sin alguno de los cuatro componentes. |
| Detección | Lectura de cada .claude/agents/*.md. |
| Por qué importa | El brief completo es lo que separa el fan-out útil del trabajo duplicado con huecos. |
| Adopción | Cada subagente recibe objetivo, formato de salida, guía sobre herramientas y fuentes, y límites claros, más reglas explícitas de escalado de esfuerzo (1 agente y 3-10 llamadas para hechos simples; 10+ subagentes para investigación compleja). |
| Fuente | https://www.anthropic.com/engineering/multi-agent-research-system |
---
name: auditor-permisos
description: >
Objetivo: evaluar las fichas de la familia PERM del catálogo §3.2 sobre este repo.
Salida: JSON con {id, veredicto, evidencia{ruta,linea,cita}} por ficha, máximo 1500 tokens.
Herramientas: Read y Grep sobre .claude/ y ~/.claude/settings.json; jq para parsear.
Límites: solo lectura; no evaluar otras familias; si falta evidencia citable, NO_VERIFICABLE.
tools: Read, Grep, Bash
model: inherit
---
P-OPS-01 — --bare en CI y SDK
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | Repos con claude -p en automatización. |
| Síntoma (ausencia) | Invocaciones headless sin --bare. |
| Detección | grep -rn 'claude -p' .github/ scripts/ Makefile 2>/dev/null | grep -v -- '--bare' |
| Por qué importa | Es la corrección de AP-MOD-05 y será el default de -p en el futuro. |
| Adopción | claude -p --bare --output-format json. Para salida estructurada, --json-schema (campo structured_output); stream-json requiere --verbose. |
| Fuente | https://code.claude.com/docs/en/headless |
P-OPS-02 — Fan-out headless probado antes del set completo
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Migraciones masivas archivo por archivo. |
| Síntoma (ausencia) | Scripts que loopean sobre cientos de archivos sin ensayo previo. |
| Detección | Revisión de los scripts de migración. |
| Por qué importa | El patrón documentado es generar la lista de archivos y loopear claude -p "Migrate $file … Return OK or FAIL." --allowedTools "Edit,Bash(git commit *)", probando con 2-3 archivos antes del set completo. |
| Adopción | Añadir un modo --dry-run o un head -3 al loop y revisar los diffs. Cuidado con la sintaxis de --allowedTools: usa reglas de permisos y el espacio en Bash(git diff *) importa. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/headless |
P-OPS-03 — --worktree para paralelismo
| Campo | Valor |
|---|
| Severidad de la ausencia | BAJA |
| Aplicabilidad | Equipos que corren varias tareas simultáneas sobre el mismo repo. |
| Síntoma (ausencia) | Varias sesiones pisando el mismo working tree. |
| Detección | git worktree list |
| Por qué importa | Evita colisiones de archivos entre sesiones concurrentes. Nota operativa: .claude/worktrees es la única excepción dentro de los protected paths de .claude. |
| Adopción | claude --worktree feature-auth. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/cli-reference |
P-CTX-01 — Handoff artifacts con tracking en JSON
| Campo | Valor |
|---|
| Severidad de la ausencia | MEDIA |
| Aplicabilidad | Trabajo de larga duración que excede una ventana de contexto. |
| Síntoma (ausencia) | Se depende solo de /compact para sesiones largas, o el archivo de progreso es Markdown. |
| Detección | ls *.json *.md 2>/dev/null | grep -iE 'progress|tasks|estado|features' |
| Por qué importa | Anthropic documenta que la compactación por sí sola «no siempre pasa instrucciones perfectamente claras al siguiente agente», y que con Sonnet 4.5 «la compactación por sí sola no bastaba para un buen rendimiento en tareas largas, así que los context resets se volvieron esenciales» — el reset se apoya en un artefacto de handoff con estado suficiente. Sobre el formato: «terminamos usando JSON para esto, porque el modelo es menos propenso a cambiar o sobrescribir inapropiadamente archivos JSON que archivos Markdown». |
| Adopción | Archivo de progreso/features en JSON (descripciones end-to-end marcadas como passing/failing), commit de git al cerrar cada sesión y verificación end-to-end antes de marcar una feature como completa. Matiz: la necesidad de resets depende del modelo — en el harness de 2026 con Opus 4.5 el autor pudo eliminarlos y dejar que la compactación automática del Agent SDK gestionara el crecimiento de contexto. |
| Fuente | https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents, https://www.anthropic.com/engineering/harness-design-long-running-apps |
P-CTX-02 — /btw y /compact dirigido
| Campo | Valor |
|---|
| Severidad de la ausencia | BAJA |
| Aplicabilidad | Uso interactivo cotidiano. |
| Síntoma (ausencia) | Preguntas laterales que entran al historial; /compact a secas. |
| Detección | Revisión del transcript. |
| Por qué importa | /btw responde preguntas laterales sin que la respuesta entre al historial; /compact <instrucciones> permite compactación dirigida, frente al antipatrón de compactación agresiva que pierde contexto sutil pero crítico. |
| Adopción | Incorporar ambos al flujo, junto con Esc (interrumpe preservando contexto) y Esc+Esc o /rewind. Desde v2.1.191, /rewind puede incluso reanudar la conversación anterior a un /clear del mismo proceso. |
| Fuente | https://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/checkpointing |
P-CTX-03 — Podar el harness cuando mejora el modelo
| Campo | Valor |
|---|
| Severidad de la ausencia | BAJA |
| Aplicabilidad | Repos con andamiaje propio (scripts de orquestación, sprints, evaluadores). |
| Síntoma (ausencia) | Andamiaje que nadie revisó desde que se escribió, con modelos de hace dos generaciones. |
| Detección | git log -1 --format=%cr -- .claude/ scripts/ |
| Por qué importa | «Cada componente de un harness codifica una suposición sobre lo que el modelo no puede hacer por sí solo, y esas suposiciones vale la pena estresarlas, tanto porque pueden ser incorrectas como porque envejecen rápido a medida que los modelos mejoran.» El caso concreto del artículo: con Opus 4.6 el autor eliminó por completo el constructo de sprints y movió el evaluador a una sola pasada al final, porque el modelo ya manejaba nativamente esa descomposición. Hay que podar el harness cuando mejora el modelo. |
| Adopción | Revisar el andamiaje en cada salto de generación de modelo. |
| Fuente | https://www.anthropic.com/engineering/harness-design-long-running-apps |
P-MCP-01 — requiresUserInteraction en operaciones destructivas
| Campo | Valor |
|---|
| Severidad de la ausencia | ALTA |
| Aplicabilidad | Servidores MCP propios con herramientas irreversibles. |
| Síntoma (ausencia) | Herramientas que borran, despliegan o mueven dinero sin ese flag. |
| Detección | Revisión del _meta de las tools del servidor propio. |
| Por qué importa | _meta["anthropic/requiresUserInteraction"]: true (booleano JSON estricto, v2.1.199+) fuerza aprobación humana por llamada: el prompt aparece incluso en acceptEdits, auto y bypassPermissions, sin opción de «no volver a preguntar»; en dontAsk la llamada se deniega. Es uno de los pocos frenos que sobreviven a bypassPermissions. |
| Adopción | Marcar toda operación irreversible. |
| Fuente | https://code.claude.com/docs/en/mcp |
§ 6 — Matriz de detección rápida
Esta sección existe para que el agente enrute sin leer el catálogo entero: primero corre el barrido, y solo abre las fichas cuya señal apareció. No sustituye a la evaluación completa de §1.1 (regla 5: toda ficha debe recibir veredicto), pero ordena el trabajo.
6.1 Síntoma observable → ficha
| Lo que se ve en un grep o jq | Ficha |
|---|
CLAUDE.md con más de 200 líneas | AP-MEM-01, AP-CTX-03 |
| «write clean code», «buenas prácticas» | AP-MEM-02 |
AGENTS.md sin @AGENTS.md ni symlink | AP-MEM-03 |
Muchas líneas @archivo.md al inicio | AP-MEM-04 |
| NUNCA / JAMÁS / PROHIBIDO en CLAUDE.md | AP-MEM-05 |
CLAUDE.md en subdirectorios | AP-MEM-08 |
MEMORY.md de más de 200 líneas o 25KB | AP-MEM-09 |
"Write(, "NotebookEdit(, "Glob( | AP-PERM-03 |
| Regla de ruta con una sola barra inicial | AP-PERM-04 |
Bash( seguido de palabra y * sin espacio | AP-PERM-05 |
npx, docker exec, devbox run, mise exec en allow | AP-PERM-06 |
(param:value) dentro de allow | AP-PERM-08 |
allow que empieza con * o con glob no anclado | AP-PERM-09 |
WebFetch(domain:*. sin el apex | AP-PERM-10 |
defaultMode en settings de proyecto | AP-PERM-11, AP-CTX-06 |
Rutas de protected paths en allow | AP-PERM-12 |
Edit(x/**) de un solo segmento | AP-PERM-13 |
bypassPermissions / --dangerously-skip-permissions | AP-PERM-14 |
deny con nombre de herramienta desnudo | AP-PERM-15 |
additionalDirectories | AP-PERM-16 |
sandbox.enabled sin failIfUnavailable | AP-SBX-01 |
// dentro de sandbox.filesystem | AP-SBX-02 |
allowedDomains con comodines amplios | AP-SBX-03 |
sandbox sin credentials | AP-SBX-04 |
ProgramData\ClaudeCode | AP-SET-01 |
allowManaged*, strict* fuera de managed | AP-SET-02 |
settings.local.json trackeado por git | AP-SET-05 |
allowed-tools sin disallowed-tools | AP-SKL-01 |
$1 en el cuerpo de una skill | AP-SKL-02 |
SKILL.md largo y solo en el directorio | AP-SKL-04 |
Mismo nombre en commands/ y skills/ | AP-SKL-06 |
description de subagente de una línea | AP-AGT-02 |
name: duplicado entre agentes | AP-AGT-05 |
model: con fecha en el ID | AP-AGT-07, AP-MOD-01 |
exit 1 en .claude/hooks/ | AP-HOOK-01 |
| Clave de hook fuera de los 30 eventos | AP-HOOK-03 |
"decision" en un hook PreToolUse | AP-HOOK-04 |
$VAR sin comillas en un hook | AP-HOOK-06 |
mcp__ sin plugin_ para servidor de plugin | AP-HOOK-07 |
Entry MCP con url y sin type | AP-MCP-01 |
"type": "sse" | AP-MCP-02 |
alwaysLoad: true repetido | AP-MCP-04 |
headers con token en .mcp.json | AP-MCP-05, AP-SET-04 |
allowedMcpServers con serverName | AP-MCP-06 |
budget_tokens | AP-MOD-02 |
temperature / top_p / top_k | AP-MOD-03 |
"effort" fuera de output_config | AP-MOD-04 |
claude -p sin --bare | AP-MOD-05, P-OPS-01 |
CLAUDE_CODE_ENABLE_AUTO_MODE | AP-MOD-06 |
claude --version muy por debajo de v2.1.217 con instalación brew/winget/apt/dnf/apk | AP-MOD-07 |
total_cost_usd en dashboards | AP-MOD-08 |
6.2 Bloque de barrido inicial
#!/usr/bin/env bash
# Barrido de auditoría — solo lectura. Cada bloque con salida no vacía activa su ficha.
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
cd "$ROOT" || exit 1
S=".claude/settings.json"; SL=".claude/settings.local.json"; U="$HOME/.claude/settings.json"
echo "== versión (F2) =="; claude --version
echo "== AP-MEM-01 =="; wc -l CLAUDE.md .claude/CLAUDE.md "$HOME/.claude/CLAUDE.md" 2>/dev/null
echo "== AP-MEM-03 =="; test -f AGENTS.md && ! grep -q '@AGENTS.md' CLAUDE.md 2>/dev/null && ! test -L CLAUDE.md && echo "AGENTS.md sin puente"
echo "== AP-MEM-05 =="; grep -niE 'NUNCA|JAMÁS|PROHIBIDO|NEVER' CLAUDE.md 2>/dev/null
echo "== AP-MEM-09 =="; wc -lc "$HOME"/.claude/projects/*/memory/MEMORY.md 2>/dev/null
echo "== AP-PERM-03 =="; grep -rnE '"(Write|NotebookEdit|Glob)\(' .claude/ "$U" 2>/dev/null
echo "== AP-PERM-04 =="; grep -rnE '"(Read|Edit)\(/[^/]' .claude/ "$U" 2>/dev/null
echo "== AP-PERM-06 =="; jq -r '.permissions.allow[]? | select(test("npx |docker exec|devbox run|mise exec|direnv exec"))' "$S" "$SL" "$U" 2>/dev/null
echo "== AP-PERM-13 =="; grep -rnE '"(Read|Edit)\([a-zA-Z0-9_.-]+/\*\*\)' .claude/ "$U" 2>/dev/null
echo "== AP-PERM-14 =="; grep -rn 'bypassPermissions\|dangerously-skip-permissions' . --include='*.json' --include='*.sh' --include='*.yml' --include='*.yaml' 2>/dev/null | grep -v node_modules
echo "== AP-SBX-01 / AP-SBX-04 =="; jq -r '.sandbox | {enabled, failIfUnavailable, credenciales: (.credentials != null)}' "$S" "$U" 2>/dev/null
echo "== AP-SET-05 =="; git ls-files --error-unmatch .claude/settings.local.json 2>/dev/null && echo "settings.local.json TRACKEADO"
echo "== AP-SKL-01 =="; grep -rln 'allowed-tools' .claude/skills .claude/commands "$HOME/.claude/skills" 2>/dev/null | xargs -r grep -L 'disallowed-tools'
echo "== AP-SKL-02 =="; grep -rn '\$1\b' .claude/skills .claude/commands 2>/dev/null
echo "== AP-AGT-05 =="; grep -h '^name:' $(find .claude/agents "$HOME/.claude/agents" -name '*.md' 2>/dev/null) 2>/dev/null | sort | uniq -d
echo "== AP-HOOK-01 =="; grep -rn 'exit 1' .claude/hooks/ "$HOME/.claude/hooks/" 2>/dev/null
echo "== AP-HOOK-03 =="; jq -r '.hooks | keys[]' "$S" "$SL" "$U" 2>/dev/null | sort -u
echo "== AP-MCP-01 =="; jq -r '.mcpServers | to_entries[] | select(.value.url and (.value.type | not)) | .key' .mcp.json "$HOME/.claude.json" 2>/dev/null
echo "== AP-MCP-02 =="; jq -r '.mcpServers | to_entries[] | select(.value.type=="sse") | .key' .mcp.json "$HOME/.claude.json" 2>/dev/null
echo "== AP-MCP-05 =="; jq -r '.mcpServers | to_entries[] | select(.value.headers) | .key + " -> headers: " + (.value.headers | keys | join(","))' .mcp.json 2>/dev/null
echo "== AP-MOD-01 / AP-MOD-02 =="; grep -rnE 'claude-(opus|sonnet|fable|haiku)-[0-9-]+-20[0-9]{6}|budget_tokens' . --include='*.json' --include='*.md' --include='*.ts' --include='*.py' 2>/dev/null | grep -v node_modules
echo "== AP-MOD-05 =="; grep -rn 'claude -p' .github/ scripts/ Makefile 2>/dev/null | grep -v -- '--bare'
Regla de lectura del barrido: salida vacía en un bloque no cierra la ficha automáticamente si el archivo correspondiente no existe. En ese caso el veredicto es NO_APLICA (o ausente en el inventario), nunca LIMPIO (regla 6 de §1.1).
§ 7 — Capadores: cuándo la configuración reprueba
El veredicto global no es un promedio ni una nota numérica: es un techo cualitativo. Un repositorio con cuarenta fichas limpias y un bypassPermissions por defecto no está “casi bien”: la única barrera que importaba está desactivada.
| Capador | Veredicto global máximo |
|---|
defaultMode: bypassPermissions fuera de contenedor o VM (AP-PERM-14) | REPROBADO |
--dangerously-skip-permissions en scripts o CI (AP-PERM-14, AP-MOD-05) | REPROBADO |
Regla deny/ask que nunca coincide: Write(...), /ruta, patrón de un segmento (AP-PERM-03, AP-PERM-04, AP-PERM-13) | REPROBADO |
Hook de política con exit 1 (AP-HOOK-01) | CON RESERVAS |
allowed-tools usado como restricción (AP-SKL-01) | CON RESERVAS |
Sandbox activo sin failIfUnavailable en flujo autónomo (AP-SBX-01) | CON RESERVAS |
CLAUDE.md efectivo de más de 200 líneas (AP-MEM-01) | CON RESERVAS |
Ningún check ejecutable en el flujo (P-VER-01 ausente) | CON RESERVAS |
Escala del veredicto global, de mejor a peor:
- GOBERNADO — sin capadores activos y sin hallazgos CRÍTICA, ALTA ni MEDIA; a lo sumo hallazgos BAJA.
- SÓLIDO — sin capadores activos y sin hallazgos CRÍTICA ni ALTA; hay al menos un hallazgo MEDIA.
- CON RESERVAS — hay al menos un capador de ese nivel activo, o algún hallazgo CRÍTICA o ALTA que no dispare ningún capador de la tabla.
- REPROBADO — hay al menos un capador de ese nivel activo.
Se toma siempre el mínimo de todos los techos activos. Si un capador REPROBADO y tres CON RESERVAS están activos, el veredicto es REPROBADO.
flowchart TD
A[Lista de hallazgos<br/>con severidad] --> B{¿Hay algún capador<br/>de nivel REPROBADO?}
B -->|Sí| R[Veredicto global:<br/>REPROBADO]
B -->|No| C{¿Hay algún capador<br/>de nivel CON RESERVAS?}
C -->|Sí| CR[Veredicto global:<br/>CON RESERVAS]
C -->|No| D{¿Hay hallazgos<br/>CRÍTICA o ALTA?}
D -->|Sí| CR
D -->|No| F{¿Hay hallazgos<br/>MEDIA?}
F -->|Sí| S[Veredicto global:<br/>SÓLIDO]
F -->|No| G[Veredicto global:<br/>GOBERNADO]
R --> E[Listar capadores_activos en el JSON<br/>con el ID de la ficha que los disparó]
CR --> E
S --> E
G --> E
Nota de aplicación: un capador solo se activa si la ficha que lo dispara tiene veredicto HALLAZGO con evidencia citable. Un NO_VERIFICABLE no capa el veredicto; se reporta en su propia lista y se explica qué faltó para verificarlo.
Orden de emisión obligatorio: JSON primero, Markdown después. El Markdown se deriva del JSON, no se escribe a mano: si los dos discrepan, gana el JSON y el informe se reconstruye.
8.1 Esquema del bloque JSON
Esquema fijo y plano (sin metaesquema, sin campos libres fuera de observaciones):
{
"catalogo_version": "1.0",
"cli_version": "2.1.217",
"catalogo_desfasado": false,
"fecha": "2026-07-22",
"alcance": ["/ruta/al/repo", "~/.claude", "managed"],
"fichas_evaluadas": 101,
"fichas_totales": 101,
"veredicto_global": "CON RESERVAS",
"capadores_activos": [
{ "id": "AP-SKL-01", "techo": "CON RESERVAS" }
],
"hallazgos": [
{
"id": "AP-SKL-01",
"severidad": "CRITICA",
"familia": "SKL",
"resumen": "La skill 'deploy' usa allowed-tools como si restringiera.",
"evidencia": {
"ruta": "/repo/.claude/skills/deploy/SKILL.md",
"linea": 4,
"cita": "allowed-tools: Read, Bash"
},
"hecho_canonico": "allowed-tools pre-aprueba herramientas durante el turno que invoca la skill y caduca con el siguiente mensaje del usuario; para restringir se usa disallowed-tools.",
"fuente": "https://code.claude.com/docs/en/skills#frontmatter-reference",
"correccion": "Reemplazar por 'disallowed-tools: Edit, Write, NotebookEdit'.",
"esfuerzo": "bajo"
}
],
"patrones_ausentes": [
{ "id": "P-VER-01", "severidad": "ALTA", "razon": "No hay comando de verificación documentado ni scripts de test." }
],
"no_verificable": [
{ "id": "AP-CTX-01", "motivo": "Sesión headless: no hay acceso al transcript." }
],
"observaciones": [
"El repo define 3 plugins locales; el catálogo v1.0 no cubre validación de manifiestos."
],
"plan_remediacion": [
{
"orden": 1,
"id": "AP-SKL-01",
"accion": "Sustituir allowed-tools por disallowed-tools en .claude/skills/deploy/SKILL.md",
"verificacion": "grep -c 'disallowed-tools' .claude/skills/deploy/SKILL.md # debe devolver 1"
}
]
}
Reglas del bloque JSON:
hallazgos[] requiere siempre evidencia completa (ruta, linea, cita) y fuente. Sin eso, la ficha va a no_verificable[].
- El campo
severidad admite exactamente cuatro literales, en mayúsculas y sin tildes: CRITICA, ALTA, MEDIA, BAJA (§2.1). La forma acentuada CRÍTICA se usa solo en la prosa y en la tabla Markdown derivada (§8.2, §8.3), nunca dentro del JSON.
plan_remediacion[] se ordena por severidad / esfuerzo (primero lo grave y barato) y cada entrada lleva verificacion: el comando exacto que demuestra que quedó arreglado.
observaciones[] no lleva severidad ni ID de ficha: es donde va todo lo que el agente notó y el catálogo no cubre (regla 4 de §1.1).
- Todo valor con forma de token o credencial se emite como
[REDACTADO] (regla 7).
8.2 Resumen ejecutivo
Máximo 10 líneas, con el veredicto global y las tres acciones principales. Ejemplo:
Veredicto global: CON RESERVAS (capador activo: AP-SKL-01).
101 fichas evaluadas de 101. 6 hallazgos: 1 CRÍTICA, 2 ALTA, 3 MEDIA. 2 patrones ausentes.
La configuración de permisos es coherente y no tiene reglas muertas.
El sandbox está activo y falla cerrado.
El punto único de riesgo es la skill 'deploy', que cree restringir herramientas y en realidad las pre-aprueba.
Top 3:
1. AP-SKL-01 — cambiar allowed-tools por disallowed-tools en .claude/skills/deploy/SKILL.md (esfuerzo bajo).
2. P-VER-01 — documentar el comando de verificación en CLAUDE.md y exigir evidencia (esfuerzo bajo).
3. AP-MEM-01 — podar CLAUDE.md de 340 a menos de 200 líneas moviendo 3 secciones a .claude/rules/ (esfuerzo medio).
8.3 Tabla Markdown
8.4 Bloque de autoverificación
Obligatorio antes de entregar (regla 9 de §1.1):
AUTOVERIFICACIÓN
fichas_evaluadas / fichas_totales : 101 / 101 -> OK
hallazgos sin evidencia : 0 -> OK (debe ser 0)
hallazgos sin fuente : 0 -> OK (debe ser 0)
capadores_activos coherentes con hallazgos: sí -> OK
Si alguno de los contadores falla, el informe no se emite: se repite la familia afectada y se recalcula.
8.5 Nota de uso headless
claude -p --output-format json --bare sirve para auditar otro repositorio desde CI, pasando las rutas como input. No sirve para auditar el propio entorno, porque --bare omite precisamente hooks, skills, plugins, MCP, auto memory y CLAUDE.md: el agente vería un entorno vacío y reportaría todo como ausente (https://code.claude.com/docs/en/headless).
La fase F6 solo arranca con autorización explícita del usuario. Regla base: proponer diff, no aplicar.
9.1 Restricciones
- Nunca escribir en managed settings ni en rutas de política de la organización (
/etc/claude-code/, /Library/Application Support/ClaudeCode/, C:\Program Files\ClaudeCode\). Esas rutas se reportan; las cambia quien administra la flota.
- Escribir preferentemente en
.claude/settings.local.json (en la raíz del repo git) y en ~/.claude/settings.json.
- Jamás secretos en archivos versionados. Si la remediación involucra una credencial ya commiteada, la acción es rotarla, no moverla.
- Recordatorio de protected paths: escribir en
.claude/ requiere aprobación salvo en bypassPermissions (que es justamente lo que no se debe usar para esto). Es esperable que cada edición dispare un prompt.
9.2 Orden de aplicación
Por severidad y por reversibilidad: primero lo grave y reversible, después lo grave e irreversible, y al final la higiene. Un cambio por commit, y cada commit seguido de su comando de verificacion del plan.
9.3 Advertencia de efecto cruzado
Obligatoria en toda corrección de permisos. Un deny no admite excepciones internas: añadir deny: ["Bash(npx *)"] anula cualquier allow de npx en cualquier scope, incluido --allowedTools. Las dos salidas posibles son:
- Acotar el allow y omitir el deny: cambiar
Bash(npx *) por Bash(npx prettier --write *) y no añadir ningún deny.
- Aceptar el bloqueo total: añadir el deny sabiendo que ningún uso de
npx quedará disponible.
No existe una tercera opción. Toda propuesta de remediación sobre permisos debe declarar cuál de las dos se está eligiendo.
stateDiagram-v2
[*] --> Propuesta: F5 entrega el plan_remediacion
Propuesta --> Aprobacion: se muestra el diff exacto
Aprobacion --> Propuesta: el usuario pide ajustes
Aprobacion --> Aplicacion: autorización explícita
Aplicacion --> Reverificacion: un cambio, un commit
Reverificacion --> Aplicacion: el comando de verificación falla
Reverificacion --> Cierre: el comando de verificación pasa
Cierre --> Aplicacion: quedan entradas en el plan
Cierre --> [*]: plan agotado y autoverificación OK
[1/6] AP-PERM-06 — CRÍTICA — esfuerzo bajo
Archivo: .claude/settings.json:12
Ahora: "Bash(npx *)"
Propuesta: "Bash(npx prettier --write *)", "Bash(npx tsc --noEmit)"
Efecto cruzado: se acota el allow; NO se añade deny, porque un deny de npx
anularía cualquier allow de npx en todos los scopes.
Verificación: jq -r '.permissions.allow[]' .claude/settings.json | grep -c '^Bash(npx \*)$' # debe ser 0
¿Aplico este cambio? (sí / no / ajustar)
§ 10 — Checklist final de homologación
Una línea por ficha CRÍTICA y ALTA, agrupada por familia, para que un humano pueda pasar la auditoría sin agente.
MEM
PERM
SBX
SET
SKL
AGT
HOOK
MCP
MOD
CTX y patrones
Apéndice A — Fuentes canónicas
Nota sobre redirecciones 308. La antigua fuente canónica https://www.anthropic.com/engineering/claude-code-best-practices responde 308 y redirige a https://code.claude.com/docs/en/best-practices, que es la vigente y tiene contenido reescrito. Igual ocurre con https://www.anthropic.com/news/how-anthropic-teams-use-claude-code, que redirige a https://claude.com/blog/how-anthropic-teams-use-claude-code. Si una configuración cita las URLs viejas como justificación, conviene revalidar contra el contenido actual: varias recomendaciones cambiaron, empezando por la desaparición del «safe YOLO mode».
Apéndice B — Tabla de obsolescencias
Permite reconocer configuración heredada aunque no la cubra una ficha explícita, y marcar NO_APLICA correctamente en instalaciones antiguas.
| ANTES | AHORA | Desde | Ficha | Fuente |
|---|
npm install -g @anthropic-ai/claude-code era el método principal | El instalador nativo (install.sh / install.ps1 / install.cmd) se auto-actualiza; npm es opción avanzada | — | AP-MOD-07 | https://code.claude.com/docs/en/overview |
Managed settings de Windows en C:\ProgramData\ClaudeCode\ | C:\Program Files\ClaudeCode\ | v2.1.75 | AP-SET-01 | https://code.claude.com/docs/en/settings |
| Cuatro modos de permiso | Seis: default (UI «Manual», alias manual), acceptEdits, plan, auto, dontAsk, bypassPermissions | v2.1.200 (alias) | AP-PERM-11 | https://code.claude.com/docs/en/permission-modes |
| Un settings de mayor precedencia reemplaza la lista de permisos | Las reglas allow/ask/deny se fusionan entre todos los scopes | — | AP-PERM-01 | https://code.claude.com/docs/en/permissions |
| La regla más específica gana; se pueden hacer excepciones dentro de un deny | Orden deny → ask → allow, primera coincidencia decide | — | AP-PERM-02 | https://code.claude.com/docs/en/permissions |
Write(path) restringe escrituras | Solo Edit(path) y Read(path) se evalúan; Write/NotebookEdit/Glob nunca coinciden | v2.1.210 | AP-PERM-03 | https://code.claude.com/docs/en/permissions |
Un deny de Read no afectaba a Edit | Un deny de Read bloquea también Edit sobre esa ruta | v2.1.208 | AP-PERM-03 | https://code.claude.com/docs/en/permissions |
/path es la raíz del filesystem | /path es relativa al origen del settings; la absoluta es //path | — | AP-PERM-04 | https://code.claude.com/docs/en/permissions |
El if de un hook con Edit(src/**) coincidía con un src a cualquier profundidad | Cubre solo el src del directorio de trabajo, igual que las reglas de permisos; para profundidad, Edit(**/src/**) | v2.1.214 (solo el if de hooks; en reglas de permisos siempre fue así) | AP-PERM-13 | https://code.claude.com/docs/en/hooks#common-fields |
defaultMode: "auto" valía desde cualquier settings | Se ignora si viene de settings de proyecto o local | v2.1.142 | AP-PERM-11 | https://code.claude.com/docs/en/permission-modes |
| «Yes, don’t ask again» se guardaba en el directorio de inicio | Se guarda en .claude/settings.local.json de la raíz del repo git y se carga desde ahí | v2.1.211 | AP-SET-05 | https://code.claude.com/docs/en/permissions |
.claude/settings.local.json tratado como suministrado por el repo | Restaurado su tratamiento como archivo local del usuario | v2.1.196→v2.1.200 | AP-SET-05 | https://code.claude.com/docs/en/permissions |
«Safe YOLO mode» con --dangerously-skip-permissions era la recomendación para autonomía | Auto mode + allowlists vía /permissions + sandbox OS-level vía /sandbox | — | AP-PERM-14, P-SEC-01 | https://code.claude.com/docs/en/best-practices |
CLAUDE_CODE_ENABLE_AUTO_MODE=1 era obligatoria en todos los proveedores | Solo lo fue en Bedrock, Agent Platform, Foundry y Claude apps gateway (nunca en la Anthropic API ni en Claude Platform on AWS); desde v2.1.207 no tiene efecto (se acepta por compatibilidad) | v2.1.207 | AP-MOD-06 | https://code.claude.com/docs/en/permission-modes |
| El clasificador de auto mode corría en el modelo de la sesión | Corre en Sonnet 5 y sus tokens se facturan | v2.1.210 | P-SEC-01 | https://code.claude.com/docs/en/permission-modes |
| El sandbox protegía credenciales y fallaba cerrado | Lee todo el equipo por defecto y falla abierto salvo failIfUnavailable: true; hace falta sandbox.credentials | v2.1.187 / v2.1.199 (mask) | AP-SBX-01, AP-SBX-04 | https://code.claude.com/docs/en/sandboxing |
| La memoria era solo CLAUDE.md | Existen además auto memory (~/.claude/projects/<p>/memory/) y .claude/rules/*.md con paths: | — | AP-MEM-06, AP-MEM-09 | https://code.claude.com/docs/en/memory |
Claude Code leía AGENTS.md automáticamente | No lo carga en runtime: hace falta @AGENTS.md o symlink | — | AP-MEM-03 | https://code.claude.com/docs/en/memory |
Los hooks tenían 9 eventos y solo handler command | 30 eventos y cinco tipos de handler (command, http, mcp_tool, prompt, agent) | — | AP-HOOK-03 | https://code.claude.com/docs/en/hooks |
exit 1 bloqueaba; exit 2 con JSON malformado dejaba pasar | Solo exit 2 bloquea; exit 2 con JSON inválido sí bloquea vía stderr | v2.1.214 | AP-HOOK-01 | https://code.claude.com/docs/en/hooks#exit-code-output |
PreToolUse se controlaba con decision: "approve"/"block" top-level | hookSpecificOutput.permissionDecision con allow | deny | ask | defer | — | AP-HOOK-04 | https://code.claude.com/docs/en/hooks#pretooluse |
| Los slash commands eran un sistema aparte | Comandos y skills fusionados; ante colisión gana la skill | — | AP-SKL-06 | https://code.claude.com/docs/en/slash-commands |
allowed-tools restringía; $1 era el primer argumento | allowed-tools pre-aprueba (restringe disallowed-tools); $N es base 0 | — | AP-SKL-01, AP-SKL-02 | https://code.claude.com/docs/en/skills#frontmatter-reference |
/agents abría un wizard; los subagentes corrían en primer plano y no podían anidar | Sin wizard, en background por defecto, heredan extended thinking; anidan hasta profundidad cinco (tope fijo, no configurable) y hasta 200 por sesión (CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION) | v2.1.172 / v2.1.198 / v2.1.212 | AP-AGT-04 | https://code.claude.com/docs/en/sub-agents |
Un subagente con tools recortado era una frontera de seguridad | No lo es: mismo proceso y misma configuración de sandbox | — | AP-AGT-01 | https://code.claude.com/docs/en/sandboxing |
/output-style cambiaba el estilo de salida | Deprecado en v2.1.73 y eliminado en v2.1.91: se usa /config o el campo outputStyle | v2.1.91 | — | https://code.claude.com/docs/en/output-styles |
| Dos transportes MCP prácticos y había que limitar servidores por tokens | Cuatro transportes (sse deprecado) y tool search activo por defecto; el riesgo es superficie de ataque | — | AP-MCP-02, AP-MCP-03 | https://code.claude.com/docs/en/mcp |
OAuth de MCP solo desde /mcp interactivo | claude mcp login <name> desde la shell, con --no-browser y --callback-port | v2.1.186 / v2.1.191 | AP-MCP-05 | https://code.claude.com/docs/en/mcp |
Las herramientas MCP siempre eran mcp__<server>__<tool> | Las de plugins son mcp__plugin_<plugin>_<server>__<tool> | — | AP-HOOK-07 | https://code.claude.com/docs/en/mcp |
| Los IDs de modelo llevaban sufijo de fecha | Desde la generación 4.6 no lo llevan (siguen siendo snapshots pinneados) | — | AP-MOD-01 | https://platform.claude.com/docs/en/about-claude/models/overview |
thinking: {type:"enabled", budget_tokens:N} | thinking: {type:"adaptive"}; el clásico solo sigue en Haiku 4.5 (y deprecado en 4.6) | — | AP-MOD-02 | https://platform.claude.com/docs/en/about-claude/models/overview |
temperature/top_p/top_k y prefill del turno assistant final | Devuelven 400; se usa prompting y structured outputs | — | AP-MOD-03 | https://platform.claude.com/docs/en/about-claude/models/overview |
effort requería cabecera beta y llegaba hasta high | GA sin beta, en output_config.effort, hasta max; xhigh recomendado para coding | — | AP-MOD-04 | https://platform.claude.com/docs/en/about-claude/models/overview |
-p conservaba las barreras de confianza de una sesión interactiva | La verificación de trust queda desactivada con -p; existe --bare (futuro default) | — | AP-MOD-05, P-OPS-01 | https://code.claude.com/docs/en/security |
Apéndice C — La auditoría empaquetada como skill
El capítulo enseña progressive disclosure y se empaqueta a sí mismo con progressive disclosure: metadata siempre en contexto, SKILL.md compacto al invocarse, catálogo y scripts solo cuando hacen falta.
Estructura:
.claude/skills/auditar-config/SKILL.md — entrypoint: protocolo de 6 fases y reglas de ejecución.
.claude/skills/auditar-config/reference.md — el catálogo completo §3-§5, se lee solo en F3.
.claude/skills/auditar-config/scripts/barrido.sh — los comandos de §6.2.
flowchart TD
A["Nivel 1 · metadata<br/>name + description + when_to_use<br/>siempre en contexto"] --> B["Nivel 2 · SKILL.md<br/>protocolo de 6 fases<br/>carga al invocar"]
B --> C["Nivel 3 · reference.md<br/>catálogo completo<br/>se lee en F3"]
B --> D["Nivel 3 · scripts/barrido.sh<br/>detección determinista<br/>se ejecuta, no se lee"]
SKILL.md:
---
name: auditar-config
description: >
Audita la configuración de Claude Code de este repositorio contra el catálogo de
antipatrones y patrones canónicos: CLAUDE.md, .claude/rules, settings.json en todos
los scopes, permisos, sandbox, skills, comandos, subagentes, hooks, MCP y flags de CLI.
Emite un informe JSON con veredicto global, hallazgos con evidencia citable y un plan
de remediación con comando de verificación por entrada.
when_to_use: >
Usar cuando el usuario pida auditar, homologar o revisar la configuración del agente;
al incorporar un repositorio nuevo; tras actualizar el CLI a una versión mayor; o antes
de habilitar un flujo autónomo (auto mode, dontAsk o headless en CI).
disable-model-invocation: true
disallowed-tools: Edit, Write, NotebookEdit
argument-hint: "[ruta del repo | familia a auditar]"
---
# Auditoría de configuración
## Alcance
Audita el repositorio en `${CLAUDE_PROJECT_DIR}` y la configuración de usuario en `~/.claude/`.
Si se pasó un argumento, restringe el alcance a lo indicado en `$0` (ruta o familia).
## Protocolo — no omitir fases
1. **F1 Inventario**: lista las rutas de configuración y registrá explícitamente las ausentes.
2. **F2 Versión**: ejecuta `claude --version` y `claude doctor`. Las fichas con versión mínima
superior a la instalada se marcan `NO_APLICA`, nunca `HALLAZGO`.
3. **F3 Evaluación**: ejecuta `${CLAUDE_SKILL_DIR}/scripts/barrido.sh` y después lee
`${CLAUDE_SKILL_DIR}/reference.md` para evaluar TODAS las fichas de las 10 familias.
4. **F4 Clasificación**: asigna severidad y evalúa los capadores del veredicto global.
5. **F5 Informe**: emite el JSON primero y la tabla Markdown derivada después.
6. **F6 Remediación**: NO la ejecutes. Propón el plan y espera autorización explícita.
## Reglas no negociables
- Trabajo en solo lectura. Esta skill declara `disallowed-tools` para garantizarlo.
- Veredicto tipado por ficha: `HALLAZGO | LIMPIO | NO_APLICA | NO_VERIFICABLE`.
- Sin evidencia citable (ruta absoluta + línea + texto literal) no hay `HALLAZGO`.
- Vocabulario cerrado: lo que no está en `reference.md` va a `observaciones`, sin severidad.
- Todo valor con forma de credencial se emite como `[REDACTADO]`.
- Solo se reporta lo que afecta corrección, seguridad o consumo de contexto.
- Antes de emitir, verifica que `fichas_evaluadas == fichas_totales` y que ningún hallazgo
carece de evidencia ni de fuente. Si falla, repite la familia afectada.
- Si el inventario supera ~40 archivos, delega una familia a un subagente pidiéndole un
resumen de 1.000-2.000 tokens.
Notas de diseño de la skill, todas verificables contra el catálogo:
disallowed-tools, no allowed-tools — es la única forma que restringe de verdad (AP-SKL-01, P-SKL-02).
disable-model-invocation: true — una auditoría se lanza a mano, no cuando el modelo cree que viene al caso (AP-SKL-07).
reference.md separado — el cuerpo del SKILL.md permanece en contexto durante los turnos siguientes; el catálogo completo solo se paga cuando se llega a F3 (AP-SKL-04, P-SKL-01).
scripts/barrido.sh — trabajo determinista resuelto con código en vez de generación de tokens (P-SKL-01).
${CLAUDE_SKILL_DIR} y ${CLAUDE_PROJECT_DIR} — rutas absolutas, que es lo que la doc de tooling recomienda por los fallos medidos con rutas relativas.
$0 y no $1 para el primer argumento — índice base 0 (AP-SKL-02).
Anterior: Preguntas de Práctica
Volver al: Índice