Cap 28: Auditoría de Configuración de Agentes

Por: Artiko
claude-codeauditoriaantipatronesconfiguracionseguridad

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:

  • /context — lista los «Memory files» efectivamente cargados (https://code.claude.com/docs/en/memory).
  • /memory — lista y permite abrir los archivos de memoria, incluida la auto memory.

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.

RutaQué esComando de listado
/Library/Application Support/ClaudeCode/managed-settings.json (macOS)Managed settingsls -l "/Library/Application Support/ClaudeCode/"
/etc/claude-code/managed-settings.json (Linux/WSL)Managed settingsls -l /etc/claude-code/
C:\Program Files\ClaudeCode\managed-settings.json (Windows)Managed settingsdir "C:\Program Files\ClaudeCode"
~/.claude/settings.jsonSettings de usuariols -l ~/.claude/
~/.claude/CLAUDE.mdMemoria de usuariowc -l ~/.claude/CLAUDE.md
~/.claude/rules/*.mdReglas de usuariols -l ~/.claude/rules/
~/.claude/skills/, ~/.claude/agents/Skills y subagentes de usuariols -R ~/.claude/skills ~/.claude/agents
~/.claude.jsonServidores MCP de scope local y userjq 'keys' ~/.claude.json
~/.claude/projects/<project>/memory/MEMORY.mdAuto memorywc -l ~/.claude/projects/*/memory/MEMORY.md
./CLAUDE.md o ./.claude/CLAUDE.mdMemoria de proyectowc -l CLAUDE.md .claude/CLAUDE.md
./CLAUDE.local.mdMemoria 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 lugarls -l CLAUDE.local.md
./AGENTS.mdInstrucciones de otros agentesls -l AGENTS.md
.claude/settings.jsonSettings de proyecto (versionado)jq . .claude/settings.json
.claude/settings.local.jsonSettings locales; desde v2.1.211 vive en la raíz del repo gitjq 'keys' "$(git rev-parse --show-toplevel)/.claude/settings.local.json"
.claude/rules/*.mdReglas path-scopedls -l .claude/rules/
.claude/skills/*/SKILL.mdSkills de proyectols .claude/skills/*/SKILL.md
.claude/commands/*.mdComandos (fusionados con skills)ls .claude/commands/
.claude/agents/**/*.mdSubagentesfind .claude/agents -name '*.md'
.mcp.jsonServidores MCP de scope projectjq . .mcp.json
.claude-plugin/plugin.json, .claude-plugin/marketplace.jsonPlugin o marketplace localls -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).

F5 — Informe

Emitir JSON primero, Markdown después (§8). El Markdown se deriva del JSON.

F6 — Remediación

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

  1. 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.
  2. Veredicto tipado por ficha: HALLAZGO | LIMPIO | NO_APLICA | NO_VERIFICABLE. No existe un quinto valor.
  3. 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.
  4. 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.
  5. NO_APLICA obligatorio y explícito. Nunca se omite una ficha en silencio: toda ficha del catálogo aparece en el conteo final.
  6. 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.
  7. 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].
  8. 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”.
  9. 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.
  10. 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:

CampoContenido
IDAP-<FAMILIA>-<NN> para antipatrón, P-<FAMILIA>-<NN> para patrón a adoptar. Vocabulario cerrado.
SeveridadCRÍTICA · ALTA · MEDIA · BAJA, con el criterio objetivo de abajo.
Síntoma detectableQué se observa en el repositorio. En las fichas P-* el síntoma es la ausencia.
DetecciónComando ejecutable. Si no devuelve nada, el veredicto es LIMPIO.
Por qué dueleConsecuencia operativa concreta. Sin juicio de valor: se describe qué pasa, no si está bien o mal.
CorrecciónAcción exacta.
MALO / BUENOPar de ejemplos mínimos.
FuenteURL 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

CampoValor
SeveridadMEDIA (capador: techo CON RESERVAS, §7)
SíntomaCualquier archivo de memoria efectivo supera las 200 líneas.
Detecciónwc -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ónPodar 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.
Fuentehttps://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

CampoValor
SeveridadBAJA
SíntomaLíneas tipo «write clean code», «usa nombres descriptivos», descripciones archivo-por-archivo, documentación de API detallada, convenciones estándar del lenguaje.
Deteccióngrep -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é dueleConsume presupuesto de atención sin cambiar comportamiento; es el combustible del AP-MEM-01.
CorrecciónEliminar. 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.
Fuentehttps://code.claude.com/docs/en/best-practices
CampoValor
SeveridadALTA
SíntomaExiste 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óntest -f AGENTS.md && ! grep -q '@AGENTS.md' CLAUDE.md 2>/dev/null && ! test -L CLAUDE.md && echo "HALLAZGO AP-MEM-03"
Por qué dueleClaude Code no lee AGENTS.md en runtime. El equipo cree tener instrucciones cargadas y el agente nunca las ve: falla silenciosa total.
CorrecciónCrear CLAUDE.md con una línea @AGENTS.md, o hacer ln -s AGENTS.md CLAUDE.md.
Fuentehttps://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

CampoValor
SeveridadMEDIA
SíntomaCLAUDE.md corto que importa media docena de archivos grandes, con comentarios del tipo «dividido para no cargar todo».
Deteccióngrep -c '^@' CLAUDE.md; grep -o '^@.*' CLAUDE.md | tr -d '@' | xargs -r wc -l 2>/dev/null
Por qué dueleLos 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ónSi el objetivo es carga condicional, usar .claude/rules/*.md con frontmatter paths: o skills; los imports quedan solo para organización.
Fuentehttps://code.claude.com/docs/en/memory

AP-MEM-05 — Regla crítica confiada a CLAUDE.md

CampoValor
SeveridadCRÍTICA
SíntomaCLAUDE.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óngrep -niE 'NUNCA|JAMÁS|PROHIBIDO|NEVER|bajo ninguna circunstancia' CLAUDE.md .claude/CLAUDE.md ~/.claude/CLAUDE.md 2>/dev/null
Por qué dueleCLAUDE.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ónTrasladar 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.
Fuentehttps://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/

CampoValor
SeveridadMEDIA
SíntomaCLAUDE.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ónls .claude/rules/*.md 2>/dev/null || grep -niE 'cuando (toques|edites|trabajes en) ' CLAUDE.md 2>/dev/null
Por qué dueleEsa sección se carga en todas las sesiones, incluidas las que nunca tocan ese directorio.
CorrecciónMover a .claude/rules/<tema>.md con frontmatter paths: (globs). Las reglas sin paths cargan al inicio con la misma prioridad que .claude/CLAUDE.md.
Fuentehttps://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-07CLAUDE.local.md en flujo con worktrees

CampoValor
SeveridadBAJA
SíntomaExiste CLAUDE.local.md y el repo usa worktrees (git worktree list devuelve más de una entrada, o hay .claude/worktrees/).
Deteccióntest -f CLAUDE.local.md && git worktree list | wc -l
Por qué dueleEl archivo vive en un solo worktree: el resto de los worktrees corren sin esas instrucciones.
CorrecciónSustituirlo por un import a un archivo del home: @~/.claude/mi-archivo.md, que es la recomendación explícita de la doc para worktrees.
Fuentehttps://code.claude.com/docs/en/memory

AP-MEM-08 — Asumir que un CLAUDE.md anidado se reinyecta tras /compact

CampoValor
SeveridadMEDIA
SíntomaReglas 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ónfind . -name CLAUDE.md -mindepth 2 -not -path './node_modules/*'
Por qué dueleEl 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ónSubir a la raíz lo que debe sobrevivir a compactación, o convertirlo en una regla .claude/rules/*.md con paths:.
Fuentehttps://code.claude.com/docs/en/memory

AP-MEM-09 — Auto memory asumida como heredada por subagentes

CampoValor
SeveridadMEDIA
SíntomaSubagentes cuyo prompt da por sabido algo que solo está en ~/.claude/projects/<project>/memory/; o MEMORY.md que supera 200 líneas / 25KB.
Detecciónwc -lc ~/.claude/projects/*/memory/MEMORY.md 2>/dev/null
Por qué dueleLa 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ónEscribir 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.
Fuentehttps://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:

  1. 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».
  2. El orden de evaluación es deny → ask → allow y la primera coincidencia decide. La especificidad no altera nada.
  3. 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

CampoValor
SeveridadALTA
Síntoma.claude/settings.local.json (o --allowedTools) intenta “sobrescribir” un deny definido en ~/.claude/settings.json o en managed.
Detecciónjq -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é dueleLas 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ónQuitar 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.
Fuentehttps://code.claude.com/docs/en/permissions#settings-precedence

AP-PERM-02 — Excepción dentro de un deny

CampoValor
SeveridadALTA
SíntomaUn deny amplio y un allow más específico que pretende ser su excepción.
Detecciónjq -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ónInvertir la lógica: acotar el deny a lo que realmente se quiere bloquear (Bash(aws ec2 terminate-instances *)) y dejar el allow específico.
Fuentehttps://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-03Write(...), NotebookEdit(...) o Glob(...) en reglas de ruta

CampoValor
SeveridadCRÍTICA (capador: REPROBADO) · Desde v2.1.210
SíntomaReglas de permisos con especificador de ruta sobre Write, NotebookEdit o Glob.
Deteccióngrep -rnE '"(Write|NotebookEdit|Glob)\(' .claude/ ~/.claude/settings.json 2>/dev/null
Por qué dueleDesde 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ónReescribir 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.
Fuentehttps://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

CampoValor
SeveridadCRÍTICA (capador: REPROBADO)
SíntomaReglas con una sola barra inicial que pretenden apuntar a la raíz del filesystem: Read(/etc/**), Edit(/Users/alice/**).
Deteccióngrep -rnE '"(Read|Edit)\(/[^/]' .claude/ ~/.claude/settings.json 2>/dev/null
Por qué dueleLas 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ónDoble barra para absolutas: Read(//etc/**).
Fuentehttps://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-05Bash(ls*) sin frontera de palabra

CampoValor
SeveridadALTA
SíntomaPatrones Bash sin espacio antes del asterisco en reglas allow.
Detecciónjq -r '.permissions.allow[]? | select(test("^Bash\\([a-zA-Z0-9_-]+\\*"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleEl 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ónEscribir Bash(ls *) o el sufijo equivalente Bash(ls:*), que solo se reconoce al final del patrón.
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-06allow sobre runners no despojados

CampoValor
SeveridadCRÍTICA
Síntomaallow que contiene Bash(devbox run *), Bash(npx *), Bash(docker exec *), Bash(mise exec *), Bash(direnv exec *).
Detecciónjq -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é dueleClaude 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ónNunca pre-aprobar el runner en genérico. Aprobar comandos concretos: Bash(devbox run test *), Bash(npx prettier *).
Fuentehttps://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

CampoValor
SeveridadMEDIA
SíntomaAllowlist diseñada suponiendo que aprobar un comando aprueba la cadena entera.
DetecciónRevisión manual de .permissions.allow + los comandos reales del historial.
Por qué dueleBash(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ónAñadir reglas por cada subcomando que se quiera pre-aprobar. En PowerShell rige la misma forma, con canonicalización de alias (gci, ls, dirGet-ChildItem).
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-08Tool(param:value) dentro de allow

CampoValor
SeveridadALTA
SíntomaEntradas tipo Agent(model:opus) o Bash(run_in_background:true) en la lista allow.
Detecciónjq -r '.permissions.allow[]? | select(test("\\([a-zA-Z_]+:"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null | grep -v '^Bash(.*:\*)$'
Por qué dueleEl 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ónMover la regla a deny o ask, que es donde se evalúa.
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-09 — Glob no anclado en el nombre de herramienta

CampoValor
SeveridadALTA
Síntomaallow con comodines en el nombre de la herramienta: "*", "mcp__*", "Web*".
Detecciónjq -r '.permissions.allow[]? | select(test("^[^(]*\\*"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueledeny 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ónAnclar: mcp__github__* es válido en allow; mcp__* no.
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-10WebFetch(domain:*.example.com) sin cubrir el apex

CampoValor
SeveridadMEDIA
SíntomaReglas WebFetch(domain:*.dominio.tld) sin la entrada correspondiente para dominio.tld.
Detecciónjq -r '.permissions | (.allow[]?,.deny[]?,.ask[]?) | select(startswith("WebFetch(domain:*."))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleWebFetch(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ónDeclarar ambas formas: WebFetch(domain:example.com) y WebFetch(domain:*.example.com).
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-11defaultMode: "auto" en settings de proyecto

CampoValor
SeveridadALTA · Desde v2.1.142
Síntoma.claude/settings.json o .claude/settings.local.json con permissions.defaultMode: "auto".
Detecciónjq -r '.permissions.defaultMode' .claude/settings.json .claude/settings.local.json 2>/dev/null
Por qué dueleDesde 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ónDeclararlo en ~/.claude/settings.json o en managed settings.
Fuentehttps://code.claude.com/docs/en/permission-modes

AP-PERM-12allow para pre-aprobar protected paths

CampoValor
SeveridadCRÍTICA
Síntomaallow 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ónjq -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é dueleLos 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ónEliminar 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.
Fuentehttps://code.claude.com/docs/en/permission-modes

AP-PERM-13Edit(src/**) esperando profundidad arbitraria

CampoValor
SeveridadCRÍ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íntomaPatrones de un solo segmento inicial: Edit(src/**), Read(secrets/**), o el campo if de un hook con la misma forma.
Deteccióngrep -rnE '"(Read|Edit)\([a-zA-Z0-9_.-]+/\*\*\)' .claude/ ~/.claude/settings.json 2>/dev/null
Por qué dueleUn 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ónEscribir Edit(**/src/**) para cualquier profundidad. Nunca marcar NO_APLICA por versión del CLI cuando el patrón está en permissions.
Fuentehttps://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-14bypassPermissions como modo habitual fuera de contenedor

CampoValor
SeveridadCRÍTICA (capador: REPROBADO)
Síntomapermissions.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óngrep -rn 'bypassPermissions|dangerously-skip-permissions' . --include='*.json' --include='*.sh' --include='*.yml' --include='*.yaml' --include='Makefile' 2>/dev/null | grep -v node_modules
Por qué dueleSalta 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ónSustituir 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".
Fuentehttps://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

CampoValor
SeveridadMEDIA
Síntomadeny: ["Bash"] cuando se quería bloquear solo algunos comandos, o deny: ["Bash(rm *)"] cuando se quería quitar la herramienta entera.
Detecciónjq -r '.permissions.deny[]? | select(test("^[A-Za-z_]+$"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleUn 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ónElegir la forma según la intención y documentarla en el propio settings.
Fuentehttps://code.claude.com/docs/en/permissions

AP-PERM-16 — Esperar que additionalDirectories cargue skills y subagentes

CampoValor
SeveridadMEDIA
Síntomapermissions.additionalDirectories apuntando a un repo de skills o subagentes compartidos.
Detecciónjq -r '.permissions.additionalDirectories[]?' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleadditionalDirectories 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ónUsar --add-dir para cargar extensibilidad, o un plugin/marketplace para distribuir skills y agentes.
Fuentehttps://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

CampoValor
SeveridadCRÍTICA (capador: CON RESERVAS en flujo autónomo)
Síntomasandbox.enabled: true sin sandbox.failIfUnavailable: true.
Detecciónjq -r '.sandbox | {enabled, failIfUnavailable}' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleFalla 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ónAñadir "failIfUnavailable": true.
Fuentehttps://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.*

CampoValor
SeveridadALTA
SíntomaRutas con doble barra inicial dentro de sandbox.filesystem.*.
Detecciónjq -r '.sandbox.filesystem | .. | strings | select(startswith("//"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué duelesandbox.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ónEscribir 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.
Fuentehttps://code.claude.com/docs/en/sandboxing

AP-SBX-03network.allowedDomains amplios sin tlsTerminate

CampoValor
SeveridadALTA
SíntomaEntradas comodín amplias (*.amazonaws.com, *.googleapis.com, *.github.io) en sandbox.network.allowedDomains sin network.tlsTerminate.
Detecciónjq -r '.sandbox.network | {allowedDomains, tlsTerminate}' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleEl 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ónEnumerar 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.
Fuentehttps://code.claude.com/docs/en/sandboxing

AP-SBX-04 — Suponer que el sandbox protege credenciales por defecto

CampoValor
SeveridadCRÍTICA
Síntomasandbox.enabled: true sin bloque sandbox.credentials, en un entorno con .env, ~/.aws/credentials, tokens en variables de entorno.
Detecciónjq -r 'has("sandbox") and (.sandbox | has("credentials"))' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué duelePor 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ónDeclarar 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.
Fuentehttps://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

CampoValor
SeveridadALTA
Síntomasandbox.enabled: true en una máquina Windows nativa o WSL1.
Detecciónuname -a; jq -r '.sandbox.enabled' ~/.claude/settings.json 2>/dev/null
Por qué dueleEl 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ónMigrar a WSL2 o a un contenedor, o asumir explícitamente la ausencia de sandbox y compensar con permissions.deny.
Fuentehttps://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\

CampoValor
SeveridadALTA · Eliminada en v2.1.75
SíntomaDocumentación interna, scripts de provisioning o MDM que despliegan a C:\ProgramData\ClaudeCode\managed-settings.json.
Deteccióngrep -rn 'ProgramData\\\\ClaudeCode|ProgramData/ClaudeCode' . 2>/dev/null | grep -v node_modules
Por qué dueleEsa ruta quedó deprecada y eliminada en v2.1.75. La política de la organización simplemente no se aplica, sin error.
CorrecciónMigrar a C:\Program Files\ClaudeCode\managed-settings.json (lo mismo para el CLAUDE.md de política y para managed-mcp.json).
Fuentehttps://code.claude.com/docs/en/settings

AP-SET-02 — Claves managed-only en user o project

CampoValor
SeveridadALTA
SíntomaClaves como allowManagedHooksOnly, strictPluginOnlyCustomization, allowManagedPermissionRulesOnly, allowManagedMcpServersOnly, blockedMarketplaces, disableSideloadFlags en ~/.claude/settings.json o .claude/settings.json.
Detecciónjq -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é dueleEstas claves no tienen efecto fuera de managed settings. El endurecimiento que el equipo cree haber aplicado no existe.
CorrecciónMoverlas 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.
Fuentehttps://code.claude.com/docs/en/permissions, https://code.claude.com/docs/en/settings

AP-SET-03 — Asumir merge de fallbackModel

CampoValor
SeveridadBAJA
SíntomafallbackModel definido en varios scopes esperando que se sumen, o con más de tres entradas.
Detecciónjq -r '.fallbackModel' .claude/settings.json ~/.claude/settings.json .claude/settings.local.json 2>/dev/null
Por qué dueleLos 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ónDeclararlo en un único scope, con hasta tres modelos.
Fuentehttps://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

CampoValor
SeveridadCRÍTICA
Síntomaenv, headers o rutas absolutas de una máquina concreta dentro del settings versionado.
Deteccióngit 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é dueleQueda 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ónMover 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.
Fuentehttps://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

CampoValor
SeveridadALTA · Desde v2.1.211
SíntomaEl archivo está trackeado por git, o existen copias en subdirectorios donde se inician sesiones.
Deteccióngit 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é dueleDesde 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ónAñadirlo a .gitignore, dejar una sola copia en la raíz del repo y borrar las de subdirectorios.
Fuentehttps://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).

AP-SKL-01allowed-tools usado como restricción

CampoValor
SeveridadCRÍTICA (capador: CON RESERVAS)
SíntomaUn 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óngrep -rln 'allowed-tools' .claude/skills .claude/commands ~/.claude/skills 2>/dev/null | xargs -r grep -L 'disallowed-tools'
Por qué dueleallowed-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ónSustituir por disallowed-tools, o mantener ambos si además se quiere pre-aprobar.
Fuentehttps://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

CampoValor
SeveridadALTA
SíntomaCuerpos de skill o comando que usan $1 esperando el primer argumento.
Deteccióngrep -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ónReindexar 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>.
Fuentehttps://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-03description + when_to_use genéricos o demasiado largos

CampoValor
SeveridadMEDIA
SíntomaDescripciones tipo «ayuda con tareas del proyecto», o la suma de description + when_to_use supera 1.536 caracteres.
Detecciónfor 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é dueledescription 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ónDescribir la categoría de tareas y los disparadores concretos, con lo crítico al principio, por debajo del límite.
Fuentehttps://code.claude.com/docs/en/skills#frontmatter-reference

AP-SKL-04SKILL.md monolítico sin progressive disclosure

CampoValor
SeveridadMEDIA
SíntomaUn SKILL.md de varios cientos de líneas, sin reference.md, examples/ ni scripts/ en el directorio.
Detecciónfor d in .claude/skills/*/; do echo "$(wc -l < "$d/SKILL.md") $d $(ls "$d" | tr '\n' ' ')"; done | sort -rn
Por qué dueleEl 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ónDividir en tres niveles: metadata (name + description) → SKILL.md compacto → archivos de apoyo referenciados. Separar contextos mutuamente excluyentes en archivos distintos.
Fuentehttps://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

CampoValor
SeveridadMEDIA
SíntomaSecciones de CLAUDE.md que describen un procedimiento que se ejecuta pocas veces (release, migración de datos, rotación de claves).
Deteccióngrep -nE '^#{2,3} ' CLAUDE.md 2>/dev/null y clasificar cada sección como “siempre” u “ocasional”.
Por qué dueleEse texto se paga en todas las sesiones. Las skills cargan bajo demanda «without bloating every conversation».
CorrecciónMover a .claude/skills/<proceso>/SKILL.md.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-SKL-06commands/x.md y skills/x/SKILL.md duplicados

CampoValor
SeveridadBAJA
SíntomaMismo nombre en ambos directorios.
Deteccióncomm -12 <(ls .claude/commands 2>/dev/null | sed 's/\.md$//' | sort) <(ls -d .claude/skills/*/ 2>/dev/null | xargs -n1 basename | sort)
Por qué dueleAnte colisión gana la skill: el archivo de commands/ es código muerto que alguien seguirá editando esperando efecto.
CorrecciónBorrar el duplicado de commands/.
Fuentehttps://code.claude.com/docs/en/slash-commands

AP-SKL-07 — Skill sin disable-model-invocation que se dispara sola

CampoValor
SeveridadMEDIA
SíntomaSkills con efectos irreversibles (deploy, migración, borrado) cuyo frontmatter no declara disable-model-invocation: true.
Deteccióngrep -rLn 'disable-model-invocation' .claude/skills/*/SKILL.md 2>/dev/null | xargs -r grep -lniE 'deploy|migrate|drop|delete|release'
Por qué dueleSin ese campo, el modelo puede invocarla por su cuenta cuando la description le parece relevante.
CorrecciónAñadir disable-model-invocation: true para dejarla exclusivamente manual.
Fuentehttps://code.claude.com/docs/en/skills#frontmatter-reference

4.2 Familia AGT — Subagentes

AP-AGT-01 — Subagente tratado como frontera de seguridad

CampoValor
SeveridadCRÍTICA
SíntomaDocumentación interna, comentarios o diseño que justifican correr algo en un subagente “porque está aislado” o “porque tiene las herramientas recortadas”.
Deteccióngrep -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ónPoner la barrera donde sí se aplica: permissions.deny, sandbox, o deshabilitar el subagente entero con Agent(NombreAgente) en deny.
Fuentehttps://code.claude.com/docs/en/sandboxing

AP-AGT-02description vaga

CampoValor
SeveridadALTA
Síntomadescription de una línea que nombra un tema sin decir objetivo, formato de salida, herramientas ni límites.
Deteccióngrep -A1 '^description:' .claude/agents/*.md 2>/dev/null
Por qué dueleAntipatró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ónReescribir con los cuatro componentes del brief (ver P-AGT-01).
Fuentehttps://www.anthropic.com/engineering/multi-agent-research-system

AP-AGT-03 — Multi-agente en tareas de código con contexto compartido

CampoValor
SeveridadMEDIA
SíntomaOrquestaciones de varios subagentes editando el mismo módulo o dependiendo unos de otros dentro de una tarea de implementación.
DetecciónRevisión de .claude/agents/ + los prompts de orquestación del repo.
Por qué dueleEconomí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ónUn 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.
Fuentehttps://www.anthropic.com/engineering/multi-agent-research-system

AP-AGT-04 — Arquitectura que ignora los topes de anidamiento y de sesión

CampoValor
SeveridadMEDIA · Desde v2.1.172 / v2.1.212
SíntomaCadenas 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óngrep -rniE 'subagente|subagent|Agent tool|delega' .claude/agents/*.md 2>/dev/null
Por qué dueleDesde 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ónAplanar 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.
Fuentehttps://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

CampoValor
SeveridadMEDIA
Síntoma.claude/agents/review/security.md y .claude/agents/infra/security.md con el mismo campo name.
Deteccióngrep -h '^name:' $(find .claude/agents ~/.claude/agents -name '*.md' 2>/dev/null) | sort | uniq -d
Por qué dueleLos 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ónNombres únicos en el campo name. Recordar la precedencia: managed settings > --agents (JSON de sesión) > .claude/agents/ > ~/.claude/agents/ > directorio agents/ de plugin.
Fuentehttps://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope

AP-AGT-06hooks, mcpServers o permissionMode en subagentes de plugin

CampoValor
SeveridadALTA
SíntomaUn subagente distribuido dentro de un plugin declara alguno de esos tres campos.
Deteccióngrep -rnE '^(hooks|mcpServers|permissionMode):' */agents/*.md .claude-plugin/../agents/*.md 2>/dev/null
Por qué dueleLos subagentes de plugin ignoran por seguridad hooks, mcpServers y permissionMode. El comportamiento declarado no ocurre y no hay error.
CorrecciónMover esa configuración al nivel del plugin o del proyecto; documentar la limitación en el README del plugin.
Fuentehttps://code.claude.com/docs/en/sub-agents#choose-the-subagent-scope

AP-AGT-07model: con ID fechado o alias retirado

CampoValor
SeveridadALTA
Síntomamodel: claude-3-5-sonnet-20241022, model: claude-opus-4-8-20260501 o similares.
Deteccióngrep -rhn '^model:' .claude/agents/*.md ~/.claude/agents/*.md 2>/dev/null
Por qué dueleDesde 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ónUsar 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.
Fuentehttps://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

CampoValor
SeveridadMEDIA
SíntomaEl prompt del subagente pide «devolvé todo lo que encuentres», «pegá el contenido de los archivos», sin límite de salida.
Deteccióngrep -rniE 'devolvé todo|pegá el|contenido completo|full output' .claude/agents/*.md 2>/dev/null
Por qué dueleEl 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ónFijar 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.
Fuentehttps://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-01exit 1 para bloquear

CampoValor
SeveridadCRÍTICA (capador: CON RESERVAS)
SíntomaUn script de hook de política termina con exit 1 en la rama de rechazo.
Deteccióngrep -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ónCambiar 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).
Fuentehttps://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

CampoValor
SeveridadCRÍTICA
SíntomaEl único mecanismo que impide una acción prohibida es un hook PreToolUse con filtro if, sin regla equivalente en permissions.deny.
Detecciónjq -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ónDuplicar la prohibición en permissions.deny y dejar el hook para logging, notificación o contexto adicional.
Fuentehttps://code.claude.com/docs/en/hooks

AP-HOOK-03 — Evento inexistente

CampoValor
SeveridadALTA
SíntomaClaves como PreSubmit, ToolUse, AgentStart, OnStart dentro de .hooks.
Detecciónjq -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é dueleEl 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ónRenombrar al evento real que corresponde a la intención.
Fuentehttps://code.claude.com/docs/en/hooks

AP-HOOK-04decision top-level en PreToolUse

CampoValor
SeveridadALTA
SíntomaUn hook PreToolUse emite {"decision": "approve"} o {"decision": "block"} en la raíz del JSON.
Deteccióngrep -rn '"decision"' .claude/hooks/ ~/.claude/hooks/ .claude/settings.json 2>/dev/null
Por qué dueleEn 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ónMigrar al esquema correcto, incluyendo hookEventName que es obligatorio dentro de hookSpecificOutput.
Fuentehttps://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

CampoValor
SeveridadMEDIA
SíntomaUn hook Stop que bloquea el fin del turno mientras no se cumpla una condición, sin salida alternativa.
Detecciónjq -r '.hooks.Stop' .claude/settings.json ~/.claude/settings.json 2>/dev/null
Por qué dueleClaude 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ónUsarlo 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.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-HOOK-06 — Script de hook sin higiene de shell

CampoValor
SeveridadALTA
SíntomaVariables sin comillas, rutas relativas, ausencia de ${CLAUDE_PROJECT_DIR}, sin bloqueo de .., o acceso a .env, .git/ y llaves.
Deteccióngrep -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ónEntrecomillar todas las variables ("$VAR"), usar rutas absolutas con ${CLAUDE_PROJECT_DIR}, validar y sanitizar entradas, rechazar path traversal (..) y evitar archivos sensibles.
Fuentehttps://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

CampoValor
SeveridadALTA
SíntomaMatcher tipo mcp__database-tools__.* cuando ese servidor viene de un plugin.
Deteccióngrep -rn 'mcp__' .claude/settings.json ~/.claude/settings.json 2>/dev/null | grep -v 'mcp__plugin_' cruzado con la lista de plugins instalados.
Por qué dueleLas 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ónEscribir el matcher con el prefijo completo del plugin.
Fuentehttps://code.claude.com/docs/en/mcp

AP-HOOK-08 — Trabajo pesado síncrono

CampoValor
SeveridadMEDIA
SíntomaHooks que corren suites de tests, builds o llamadas de red lentas sin async: true.
Deteccióngrep -rn 'command' .claude/settings.json | grep -iE 'test|build|npm run|pytest|cargo' y comprobar la ausencia de "async": true.
Por qué dueleUn 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ónMarcar async: true (con asyncRewake si hace falta reanudar) o mover el trabajo pesado a CI.
Fuentehttps://code.claude.com/docs/en/hooks

AP-HOOK-09 — Esperar que stdout inyecte contexto donde no lo hace

CampoValor
SeveridadMEDIA
SíntomaUn hook PostToolUse, Notification o SessionEnd imprime texto por stdout esperando que Claude lo lea.
Detecciónjq -r '.hooks | to_entries[] | select(.key | IN("UserPromptSubmit","UserPromptExpansion","SessionStart") | not) | .key' .claude/settings.json 2>/dev/null y revisar esos scripts.
Por qué dueleCon 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ónUsar 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.
Fuentehttps://code.claude.com/docs/en/hooks#json-output

AP-HOOK-10 — Salidas de más de 10.000 caracteres

CampoValor
SeveridadBAJA
SíntomaHooks que emiten diffs completos, logs de build o listados largos.
DetecciónEjecutar el hook con una entrada representativa y medir: bash .claude/hooks/x.sh < fixture.json | wc -c
Por qué dueleLas cadenas del JSON de salida se truncan a 10.000 caracteres: el final del mensaje —donde suele estar la conclusión— desaparece.
CorrecciónResumir en el script y, si hace falta el detalle, escribirlo a un archivo y devolver la ruta.
Fuentehttps://code.claude.com/docs/en/hooks#json-output

4.4 Familia MCP — Servidores MCP

AP-MCP-01 — Entry con url sin type

CampoValor
SeveridadALTA
SíntomaUn servidor en .mcp.json o ~/.claude.json declara url y no declara type.
Detecciónjq -r '.mcpServers | to_entries[] | select(.value.url and (.value.type | not)) | .key' .mcp.json ~/.claude.json 2>/dev/null
Por qué duelestdio 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ónDeclarar "type": "http" (o su alias streamable-http).
Fuentehttps://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

CampoValor
SeveridadMEDIA
Síntoma"type": "sse" en algún servidor.
Detecciónjq -r '.mcpServers | to_entries[] | select(.value.type=="sse") | .key' .mcp.json ~/.claude.json 2>/dev/null
Por qué duelesse 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ónMigrar a http si el servidor lo soporta.
Fuentehttps://code.claude.com/docs/en/mcp

AP-MCP-03 — Podar servidores “para ahorrar contexto”

CampoValor
SeveridadBAJA
SíntomaComentarios o commits que eliminan servidores MCP citando consumo de tokens de arranque.
Deteccióngit log --oneline -- .mcp.json | head -20 y revisar los mensajes.
Por qué dueleCon 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ónDecidir 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.
Fuentehttps://code.claude.com/docs/en/mcp

AP-MCP-04alwaysLoad: true abusado

CampoValor
SeveridadMEDIA · Desde v2.1.121
SíntomaVarios servidores con "alwaysLoad": true.
Detecciónjq -r '.mcpServers | to_entries[] | select(.value.alwaysLoad==true) | .key' .mcp.json ~/.claude.json 2>/dev/null
Por qué duelealwaysLoad 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ónDejarlo solo donde el servidor es imprescindible desde el primer turno. Alternativa de grano fino: "anthropic/alwaysLoad": true en el _meta de una herramienta concreta.
Fuentehttps://code.claude.com/docs/en/mcp

AP-MCP-05 — Secretos en .mcp.json

CampoValor
SeveridadCRÍTICA
SíntomaTokens en headers o en env dentro de .mcp.json (versionado).
Detecciónjq -r '.mcpServers | to_entries[] | select(.value.headers or .value.env) | .key + ": " + ((.value.headers // .value.env) | keys | join(","))' .mcp.json 2>/dev/null
Por qué dueleQueda en el repositorio y en cada clone. Se reporta la ruta y el nombre de la cabecera, con el valor como [REDACTADO].
CorrecciónUsar 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.
Fuentehttps://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-06allowedMcpServers filtrando por serverName

CampoValor
SeveridadCRÍTICA
SíntomaPolítica de organización que restringe servidores usando el campo serverName.
Detecciónjq -r '.allowedMcpServers' /etc/claude-code/managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json" 2>/dev/null
Por qué dueleserverName 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ónFiltrar 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.
Fuentehttps://code.claude.com/docs/en/managed-mcp

AP-MCP-07 — Payloads sin paginado

CampoValor
SeveridadMEDIA
SíntomaServidores propios cuyas herramientas devuelven colecciones completas sin parámetros de límite, filtro o rango.
DetecciónEjecutar una llamada representativa y observar el aviso de salida grande, o revisar el esquema de las tools del servidor propio.
Por qué dueleHay 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ónAñ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.
Fuentehttps://code.claude.com/docs/en/mcp, https://www.anthropic.com/engineering/writing-tools-for-agents

AP-MCP-08 — Scope mal elegido

CampoValor
SeveridadMEDIA
SíntomaEl mismo nombre de servidor definido en varios scopes con configuraciones distintas.
Detecciónjq -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é dueleLa 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ónDefinir cada servidor en un solo scope: project (.mcp.json) para lo compartido, user para lo personal transversal, local solo para experimentos.
Fuentehttps://code.claude.com/docs/en/mcp

AP-MCP-09 — Servidores que traen contenido externo sin evaluar prompt injection

CampoValor
SeveridadCRÍTICA
SíntomaServidores que leen issues, correos, páginas web o tickets, combinados con permisos amplios de escritura o de Bash.
Detecciónjq -r '.mcpServers | keys[]' .mcp.json ~/.claude.json 2>/dev/null y clasificar cuáles ingieren contenido de terceros.
Por qué dueleModelo 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ónDefensa 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.
Fuentehttps://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

CampoValor
SeveridadALTA
SíntomaCadenas como claude-opus-4-8-20260501, claude-sonnet-5-20260615 en settings, subagentes, código o CI.
Deteccióngrep -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é dueleDesde 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ónUsar 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.
Fuentehttps://platform.claude.com/docs/en/about-claude/models/overview

AP-MOD-02thinking.budget_tokens en modelos que lo eliminaron

CampoValor
SeveridadALTA
SíntomaCó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óngrep -rn 'budget_tokens' . --include='*.ts' --include='*.js' --include='*.py' --include='*.json' 2>/dev/null | grep -v node_modules
Por qué dueleEstá 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ónSustituir 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.
Fuentehttps://platform.claude.com/docs/en/build-with-claude/effort · https://platform.claude.com/docs/en/about-claude/models/migration-guide

AP-MOD-03temperature/top_p/top_k o prefill del turno assistant final

CampoValor
SeveridadALTA
SíntomaLlamadas con parámetros de sampling, o con un turno assistant final prellenado para forzar formato.
Deteccióngrep -rnE 'temperature|top_p|top_k' . --include='*.ts' --include='*.py' 2>/dev/null | grep -v node_modules
Por qué dueletemperature, 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ónDirigir 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).
Fuentehttps://platform.claude.com/docs/en/about-claude/models/overview, https://platform.claude.com/docs/en/build-with-claude/structured-outputs

AP-MOD-04effort top-level en vez de output_config.effort

CampoValor
SeveridadALTA
SíntomaPayloads con "effort": "high" en la raíz, o con la cabecera beta effort-2025-11-24.
Deteccióngrep -rn '"effort"|effort-2025-11-24' . --include='*.ts' --include='*.py' --include='*.json' 2>/dev/null | grep -v node_modules
Por qué dueleEl 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ónMover 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.
Fuentehttps://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

CampoValor
SeveridadCRÍTICA si el pipeline procesa contenido externo (capador: REPROBADO junto a AP-PERM-14); ALTA en el resto
SíntomaWorkflows de CI con claude -p sin --bare, especialmente si el prompt incluye issues, PRs de terceros, comentarios o payloads de webhook.
Deteccióngrep -rn 'claude -p|claude --print' .github/ .gitlab-ci.yml Makefile scripts/ 2>/dev/null | grep -v -- '--bare'
Por qué dueleLa 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ónAñ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.
Fuentehttps://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-06CLAUDE_CODE_ENABLE_AUTO_MODE=1 residual

CampoValor
SeveridadBAJA · Sin efecto desde v2.1.207
SíntomaLa variable exportada en dotfiles, Dockerfiles o workflows.
Deteccióngrep -rn 'CLAUDE_CODE_ENABLE_AUTO_MODE' . ~/.zshrc ~/.bashrc 2>/dev/null | grep -v node_modules
Por qué dueleEntre 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ónEliminarla. Auto mode está generalizado en todos los planes y superficies.
Fuentehttps://code.claude.com/docs/en/permission-modes

AP-MOD-07 — Instalación sin auto-actualización

CampoValor
SeveridadMEDIA
SíntomaInstalació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ónclaude --version; which claude; claude doctor
Por qué dueleLas 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ónMigrar 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.
Fuentehttps://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-08total_cost_usd leído como facturación

CampoValor
SeveridadBAJA
SíntomaDashboards, alertas o reportes que tratan total_cost_usd / costUSD como dato autoritativo.
Deteccióngrep -rn 'total_cost_usd|costUSD' . --include='*.ts' --include='*.py' --include='*.sh' 2>/dev/null | grep -v node_modules
Por qué dueleSon 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ónContrastar contra la facturación real de la plataforma y documentar la estimación como tal.
Fuentehttps://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-01Kitchen sink session

CampoValor
SeveridadMEDIA
SíntomaSesiones que mezclan tareas no relacionadas (arreglar un bug, escribir un README, revisar dependencias).
DetecciónRevisión del transcript o de ~/.claude/projects/<project>/ para ver la duración y el salto temático de las sesiones.
Por qué dueleCada tarea deja residuo en el contexto y degrada las siguientes.
Corrección/clear entre tareas no relacionadas.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-CTX-02Correcting over and over

CampoValor
SeveridadMEDIA
SíntomaTres o más correcciones sobre el mismo punto dentro de una sesión.
DetecciónRevisión del transcript.
Por qué dueleEl contexto queda contaminado con enfoques fallidos. «A clean session with a better prompt almost always outperforms a long session with accumulated corrections.»
CorrecciónRegla de las dos correcciones: a la tercera, /clear y reescribir el prompt inicial incorporando lo aprendido.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-CTX-03Over-specified CLAUDE.md

CampoValor
SeveridadMEDIA
SíntomaReglas de proceso en CLAUDE.md que se repiten y se siguen incumpliendo.
Deteccióngrep -nE 'siempre|nunca|antes de|después de' CLAUDE.md 2>/dev/null | wc -l
Por qué dueleEs el mismo diagnóstico de AP-MEM-01: cuanto más largo el archivo, más se pierde la regla.
CorrecciónConvertir la regla en hook (si debe ocurrir sin excepción) o en skill (si es ocasional), y podar el texto.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-CTX-04Trust-then-verify gap

CampoValor
SeveridadALTA
SíntomaFlujos que aceptan aserciones de éxito («listo», «los tests pasan») sin evidencia.
DetecciónAusencia de comandos de verificación en los prompts, en /goal, en hooks Stop y en CI. Ver P-VER-01.
Por qué dueleSin 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ónPedir evidencia: salida de tests, comando y resultado, screenshot. Ver los cuatro niveles de forzado en P-VER-01.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-CTX-05Infinite exploration

CampoValor
SeveridadMEDIA
SíntomaPrompts de investigación sin acotar («entiende el proyecto», «revisa todo el código»).
DetecciónRevisión del transcript: cientos de archivos leídos antes del primer cambio.
Por qué dueleInvestigar sin acotar llena el contexto con cientos de archivos y no queda presupuesto para el trabajo.
CorrecciónAcotar el alcance («lee solo src/auth/») o delegar en un subagente que devuelva un resumen de 1.000-2.000 tokens.
Fuentehttps://code.claude.com/docs/en/best-practices

AP-CTX-06 — Plan mode para diffs de una frase

CampoValor
SeveridadBAJA
SíntomaPlan mode forzado por defecto (permissions.defaultMode: "plan") para todo tipo de cambio.
Detecciónjq -r '.permissions.defaultMode' ~/.claude/settings.json .claude/settings.json 2>/dev/null
Por qué duelePlan 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ónActivarlo 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.
Fuentehttps://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

CampoValor
Severidad de la ausenciaALTA (capador: CON RESERVAS)
AplicabilidadTodo 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óngrep -niE 'test|lint|build|typecheck' CLAUDE.md 2>/dev/null; jq -r '.scripts | keys[]' package.json 2>/dev/null
Por qué importaSin un check, «looks done» es la única señal. «If you can’t verify it, don’t ship it.»
AdopciónDocumentar el comando de verificación en CLAUDE.md y escalar por niveles según criticidad.
Fuentehttps://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

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadRepos 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ónBuscar un subagente o skill de revisión: ls .claude/agents/ .claude/skills/ 2>/dev/null | grep -iE 'review|revis'
Por qué importaUn contexto fresco no está sesgado hacia el código que acaba de escribir. Existe la skill incluida /code-review.
AdopciónLanzar 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».
Fuentehttps://code.claude.com/docs/en/best-practices

P-VER-03 — Writer/Reviewer en dos sesiones

CampoValor
Severidad de la ausenciaBAJA
AplicabilidadTrabajo autónomo de larga duración.
Síntoma (ausencia)El mismo hilo escribe y evalúa su propio trabajo.
DetecciónRevisió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ónDos sesiones separadas (o --worktree), una escribe y otra revisa.
Fuentehttps://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

CampoValor
Severidad de la ausenciaALTA
AplicabilidadCualquier 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ónjq -r '{defaultMode: .permissions.defaultMode, allow: (.permissions.allow | length), sandbox: .sandbox.enabled}' ~/.claude/settings.json 2>/dev/null
Por qué importaLa 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ónActivar 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.
Fuentehttps://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

CampoValor
Severidad de la ausenciaALTA
AplicabilidadmacOS, Linux o WSL2 (AP-SBX-05).
Síntoma (ausencia)sandbox.enabled: true sin failIfUnavailable ni credentials.
Detecciónjq -r '.sandbox | {enabled, failIfUnavailable, credentials: (.credentials != null)}' ~/.claude/settings.json 2>/dev/null
Por qué importaEs la contracara positiva de AP-SBX-01 y AP-SBX-04.
AdopciónfailIfUnavailable: 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).
Fuentehttps://code.claude.com/docs/en/sandboxing

P-SEC-03 — Endurecimiento organizacional en managed settings

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadOrganizaciones con despliegue gestionado (MDM, imagen corporativa).
Síntoma (ausencia)No hay managed settings, o existen pero sin las claves de endurecimiento.
Detecciónjq -r 'keys[]' /etc/claude-code/managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json" 2>/dev/null
Por qué importaLos managed settings no se sobrescriben ni con flags de CLI.
Adopciónpermissions.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).
Fuentehttps://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:

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadRepos con áreas heterogéneas (monorepos, infra + app, front + back).
Síntoma (ausencia)Todo el conocimiento vive en un CLAUDE.md único.
Detecciónls .claude/rules/*.md 2>/dev/null || echo "AUSENTE"
Por qué importaEs «la alternativa recomendada a un CLAUDE.md gigante»: markdown modular que carga solo al tocar archivos que matcheen los globs.
AdopciónUn archivo por área con frontmatter paths:. Las reglas sin paths cargan al inicio con la misma prioridad que .claude/CLAUDE.md.
Fuentehttps://code.claude.com/docs/en/memory

P-MEM-02 — Poda periódica de la memoria

CampoValor
Severidad de la ausenciaBAJA
AplicabilidadTodo 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óngit 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ónPasar cada línea por el criterio «¿quitar esta línea haría que Claude se equivoque?».
Fuentehttps://code.claude.com/docs/en/best-practices

P-SKL-01 — Progressive disclosure de tres niveles y scripts deterministas

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadRepos con al menos una skill.
Síntoma (ausencia)Skills de un solo archivo largo, sin reference.md ni scripts/.
Detecciónls .claude/skills/*/ 2>/dev/null
Por qué importaLos 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ónDividir 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.
Fuentehttps://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills

P-SKL-02disallowed-tools para restricción real

CampoValor
Severidad de la ausenciaALTA
AplicabilidadSkills que deben ser de solo lectura o de alcance acotado.
Síntoma (ausencia)Skills de análisis o auditoría sin disallowed-tools.
Deteccióngrep -rL 'disallowed-tools' .claude/skills/*/SKILL.md 2>/dev/null
Por qué importaEs la contracara de AP-SKL-01: allowed-tools pre-aprueba, disallowed-tools restringe.
Adopcióndisallowed-tools: Edit, Write, NotebookEdit en toda skill de análisis.
Fuentehttps://code.claude.com/docs/en/skills#frontmatter-reference

P-AGT-01 — Brief de delegación completo

CampoValor
Severidad de la ausenciaALTA
AplicabilidadRepos con subagentes definidos.
Síntoma (ausencia)description sin alguno de los cuatro componentes.
DetecciónLectura de cada .claude/agents/*.md.
Por qué importaEl brief completo es lo que separa el fan-out útil del trabajo duplicado con huecos.
AdopciónCada 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).
Fuentehttps://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

CampoValor
Severidad de la ausenciaALTA
AplicabilidadRepos con claude -p en automatización.
Síntoma (ausencia)Invocaciones headless sin --bare.
Deteccióngrep -rn 'claude -p' .github/ scripts/ Makefile 2>/dev/null | grep -v -- '--bare'
Por qué importaEs la corrección de AP-MOD-05 y será el default de -p en el futuro.
Adopciónclaude -p --bare --output-format json. Para salida estructurada, --json-schema (campo structured_output); stream-json requiere --verbose.
Fuentehttps://code.claude.com/docs/en/headless

P-OPS-02 — Fan-out headless probado antes del set completo

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadMigraciones masivas archivo por archivo.
Síntoma (ausencia)Scripts que loopean sobre cientos de archivos sin ensayo previo.
DetecciónRevisión de los scripts de migración.
Por qué importaEl 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ónAñ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.
Fuentehttps://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/headless

P-OPS-03--worktree para paralelismo

CampoValor
Severidad de la ausenciaBAJA
AplicabilidadEquipos que corren varias tareas simultáneas sobre el mismo repo.
Síntoma (ausencia)Varias sesiones pisando el mismo working tree.
Deteccióngit worktree list
Por qué importaEvita colisiones de archivos entre sesiones concurrentes. Nota operativa: .claude/worktrees es la única excepción dentro de los protected paths de .claude.
Adopciónclaude --worktree feature-auth.
Fuentehttps://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/cli-reference

P-CTX-01 — Handoff artifacts con tracking en JSON

CampoValor
Severidad de la ausenciaMEDIA
AplicabilidadTrabajo 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ónls *.json *.md 2>/dev/null | grep -iE 'progress|tasks|estado|features'
Por qué importaAnthropic 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ónArchivo 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.
Fuentehttps://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

CampoValor
Severidad de la ausenciaBAJA
AplicabilidadUso interactivo cotidiano.
Síntoma (ausencia)Preguntas laterales que entran al historial; /compact a secas.
DetecciónRevisió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ónIncorporar 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.
Fuentehttps://code.claude.com/docs/en/best-practices, https://code.claude.com/docs/en/checkpointing

P-CTX-03 — Podar el harness cuando mejora el modelo

CampoValor
Severidad de la ausenciaBAJA
AplicabilidadRepos 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óngit 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ónRevisar el andamiaje en cada salto de generación de modelo.
Fuentehttps://www.anthropic.com/engineering/harness-design-long-running-apps

P-MCP-01requiresUserInteraction en operaciones destructivas

CampoValor
Severidad de la ausenciaALTA
AplicabilidadServidores MCP propios con herramientas irreversibles.
Síntoma (ausencia)Herramientas que borran, despliegan o mueven dinero sin ese flag.
DetecciónRevisió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ónMarcar toda operación irreversible.
Fuentehttps://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 jqFicha
CLAUDE.md con más de 200 líneasAP-MEM-01, AP-CTX-03
«write clean code», «buenas prácticas»AP-MEM-02
AGENTS.md sin @AGENTS.md ni symlinkAP-MEM-03
Muchas líneas @archivo.md al inicioAP-MEM-04
NUNCA / JAMÁS / PROHIBIDO en CLAUDE.mdAP-MEM-05
CLAUDE.md en subdirectoriosAP-MEM-08
MEMORY.md de más de 200 líneas o 25KBAP-MEM-09
"Write(, "NotebookEdit(, "Glob(AP-PERM-03
Regla de ruta con una sola barra inicialAP-PERM-04
Bash( seguido de palabra y * sin espacioAP-PERM-05
npx, docker exec, devbox run, mise exec en allowAP-PERM-06
(param:value) dentro de allowAP-PERM-08
allow que empieza con * o con glob no ancladoAP-PERM-09
WebFetch(domain:*. sin el apexAP-PERM-10
defaultMode en settings de proyectoAP-PERM-11, AP-CTX-06
Rutas de protected paths en allowAP-PERM-12
Edit(x/**) de un solo segmentoAP-PERM-13
bypassPermissions / --dangerously-skip-permissionsAP-PERM-14
deny con nombre de herramienta desnudoAP-PERM-15
additionalDirectoriesAP-PERM-16
sandbox.enabled sin failIfUnavailableAP-SBX-01
// dentro de sandbox.filesystemAP-SBX-02
allowedDomains con comodines ampliosAP-SBX-03
sandbox sin credentialsAP-SBX-04
ProgramData\ClaudeCodeAP-SET-01
allowManaged*, strict* fuera de managedAP-SET-02
settings.local.json trackeado por gitAP-SET-05
allowed-tools sin disallowed-toolsAP-SKL-01
$1 en el cuerpo de una skillAP-SKL-02
SKILL.md largo y solo en el directorioAP-SKL-04
Mismo nombre en commands/ y skills/AP-SKL-06
description de subagente de una líneaAP-AGT-02
name: duplicado entre agentesAP-AGT-05
model: con fecha en el IDAP-AGT-07, AP-MOD-01
exit 1 en .claude/hooks/AP-HOOK-01
Clave de hook fuera de los 30 eventosAP-HOOK-03
"decision" en un hook PreToolUseAP-HOOK-04
$VAR sin comillas en un hookAP-HOOK-06
mcp__ sin plugin_ para servidor de pluginAP-HOOK-07
Entry MCP con url y sin typeAP-MCP-01
"type": "sse"AP-MCP-02
alwaysLoad: true repetidoAP-MCP-04
headers con token en .mcp.jsonAP-MCP-05, AP-SET-04
allowedMcpServers con serverNameAP-MCP-06
budget_tokensAP-MOD-02
temperature / top_p / top_kAP-MOD-03
"effort" fuera de output_configAP-MOD-04
claude -p sin --bareAP-MOD-05, P-OPS-01
CLAUDE_CODE_ENABLE_AUTO_MODEAP-MOD-06
claude --version muy por debajo de v2.1.217 con instalación brew/winget/apt/dnf/apkAP-MOD-07
total_cost_usd en dashboardsAP-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.

CapadorVeredicto 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.


§ 8 — Formato del informe

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

IDSeveridadArchivo:líneaEvidenciaCorrecciónFuente
AP-SKL-01CRÍTICA.claude/skills/deploy/SKILL.md:4allowed-tools: Read, BashReemplazar por disallowed-tools: Edit, Write, NotebookEdithttps://code.claude.com/docs/en/skills#frontmatter-reference
AP-MEM-01MEDIACLAUDE.md:1-340340 líneasPodar y mover a .claude/rules/https://code.claude.com/docs/en/best-practices

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).


§ 9 — Remediación segura

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:

  1. Acotar el allow y omitir el deny: cambiar Bash(npx *) por Bash(npx prettier --write *) y no añadir ningún deny.
  2. 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.

9.4 Ciclo de remediación

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

9.5 Formato de una propuesta

[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

  • AP-MEM-03 — Si existe AGENTS.md, hay un CLAUDE.md que lo importa con @AGENTS.md o un symlink.
  • AP-MEM-05 — Ninguna regla crítica depende solo de CLAUDE.md; está en permissions.deny o en un hook con exit 2.

PERM

  • AP-PERM-01 — No hay allow que pretenda anular un deny de otro scope.
  • AP-PERM-02 — No hay allow que pretenda ser excepción de un deny.
  • AP-PERM-03 — Ninguna regla de ruta usa Write(, NotebookEdit( o Glob(.
  • AP-PERM-04 — Las rutas absolutas usan //.
  • AP-PERM-05 — Los patrones Bash de allow tienen frontera de palabra.
  • AP-PERM-06 — Ningún runner (npx, docker exec, devbox run, mise exec, direnv exec) está pre-aprobado en genérico.
  • AP-PERM-08 — No hay Tool(param:value) en allow.
  • AP-PERM-09 — Los allow de MCP están anclados al prefijo mcp__<server>__.
  • AP-PERM-11defaultMode: "auto" no está en settings de proyecto.
  • AP-PERM-12 — Ningún protected path aparece en allow.
  • AP-PERM-13 — Los patrones de ruta usan **/x/** cuando se espera profundidad arbitraria.
  • AP-PERM-14 — No se usa bypassPermissions ni --dangerously-skip-permissions fuera de contenedor o VM.

SBX

  • AP-SBX-01sandbox.failIfUnavailable: true cuando el sandbox está activo.
  • AP-SBX-02 — Las rutas de sandbox.filesystem.* usan convención estándar, sin //.
  • AP-SBX-03network.allowedDomains enumera hosts concretos, o hay tlsTerminate.
  • AP-SBX-04sandbox.credentials está declarado, en user o managed.
  • AP-SBX-05 — La plataforma soporta sandbox (macOS, Linux o WSL2).

SET

  • AP-SET-01 — Los managed settings de Windows están en C:\Program Files\ClaudeCode\.
  • AP-SET-02 — Las claves managed-only están solo en managed settings.
  • AP-SET-04 — No hay secretos ni rutas de máquina en .claude/settings.json versionado.
  • AP-SET-05.claude/settings.local.json está en .gitignore y vive en la raíz del repo git.

SKL

  • AP-SKL-01 — Ninguna skill usa allowed-tools creyendo que restringe.
  • AP-SKL-02 — Las sustituciones $N usan índice base 0.

AGT

  • AP-AGT-01 — Ningún subagente se trata como frontera de seguridad.
  • AP-AGT-02 — Toda description de subagente tiene objetivo, formato de salida, herramientas y límites.
  • AP-AGT-04 — Ninguna arquitectura encadena más de cinco niveles de subagentes ni depende de superar los 200 por sesión.
  • AP-AGT-06 — Los subagentes de plugin no declaran hooks, mcpServers ni permissionMode.
  • AP-AGT-07 — Los IDs de modelo son vigentes y sin sufijo de fecha.

HOOK

  • AP-HOOK-01 — Los hooks de política usan exit 2.
  • AP-HOOK-02 — Ninguna prohibición depende solo de un hook; está duplicada en permissions.deny.
  • AP-HOOK-03 — Todos los eventos declarados están entre los 30 reales.
  • AP-HOOK-04 — PreToolUse usa hookSpecificOutput.permissionDecision.
  • AP-HOOK-06 — Los scripts de hook entrecomillan variables, usan ${CLAUDE_PROJECT_DIR} y bloquean ...
  • AP-HOOK-07 — Los matchers de servidores de plugin usan el prefijo mcp__plugin_<plugin>_<server>__.

MCP

  • AP-MCP-01 — Todo entry con url declara type.
  • AP-MCP-05 — No hay secretos en .mcp.json; se usa headersHelper u OAuth.
  • AP-MCP-06 — El filtrado organizacional usa serverUrl o serverCommand, no serverName.
  • AP-MCP-09 — Los servidores que ingieren contenido externo están evaluados contra prompt injection.

MOD

  • AP-MOD-01 — Ningún ID de modelo de generación ≥ 4.6 lleva sufijo de fecha.
  • AP-MOD-02 — No se usa thinking.budget_tokens en modelos que lo eliminaron.
  • AP-MOD-03 — No se usan temperature, top_p, top_k ni prefill del turno assistant final.
  • AP-MOD-04 — El esfuerzo va en output_config.effort.
  • AP-MOD-05claude -p en CI usa --bare y --allowedTools mínimo.

CTX y patrones

  • AP-CTX-04 — Existe verificación ejecutable y se pide evidencia, no aserciones.
  • P-VER-01 — El comando de verificación está documentado y forzado en al menos un nivel.
  • P-SEC-01 — Autonomía apoyada en auto mode + allowlist + sandbox, no en saltar permisos.
  • P-SKL-02 — Las skills de solo lectura declaran disallowed-tools.
  • P-OPS-01 — Las invocaciones headless usan --bare.
  • P-MCP-01 — Las operaciones MCP destructivas declaran requiresUserInteraction.

Apéndice A — Fuentes canónicas

FamiliaURLs citadas
MEMhttps://code.claude.com/docs/en/memory · https://code.claude.com/docs/en/best-practices
PERMhttps://code.claude.com/docs/en/permissions · https://code.claude.com/docs/en/permission-modes
SBXhttps://code.claude.com/docs/en/sandboxing · https://code.claude.com/docs/en/settings#sandbox-settings
SEThttps://code.claude.com/docs/en/settings · https://code.claude.com/docs/en/permissions
SKLhttps://code.claude.com/docs/en/skills · https://code.claude.com/docs/en/skills#frontmatter-reference · https://code.claude.com/docs/en/slash-commands · https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills
AGThttps://code.claude.com/docs/en/sub-agents · https://code.claude.com/docs/en/sandboxing · https://platform.claude.com/docs/en/about-claude/model-deprecations · https://www.anthropic.com/engineering/multi-agent-research-system · https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md
HOOKhttps://code.claude.com/docs/en/hooks · https://code.claude.com/docs/en/hooks#exit-code-output · https://code.claude.com/docs/en/hooks#json-output · https://code.claude.com/docs/en/hooks#common-fields
MCPhttps://code.claude.com/docs/en/mcp · https://code.claude.com/docs/en/managed-mcp · https://www.anthropic.com/engineering/writing-tools-for-agents
MODhttps://platform.claude.com/docs/en/about-claude/models/overview · https://code.claude.com/docs/en/model-config · https://code.claude.com/docs/en/headless · https://code.claude.com/docs/en/security · https://code.claude.com/docs/en/setup · https://code.claude.com/docs/en/agent-sdk/cost-tracking
CTX y patroneshttps://code.claude.com/docs/en/best-practices · https://code.claude.com/docs/en/checkpointing · https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents · https://www.anthropic.com/engineering/harness-design-long-running-apps · https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents

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.

ANTESAHORADesdeFichaFuente
npm install -g @anthropic-ai/claude-code era el método principalEl instalador nativo (install.sh / install.ps1 / install.cmd) se auto-actualiza; npm es opción avanzadaAP-MOD-07https://code.claude.com/docs/en/overview
Managed settings de Windows en C:\ProgramData\ClaudeCode\C:\Program Files\ClaudeCode\v2.1.75AP-SET-01https://code.claude.com/docs/en/settings
Cuatro modos de permisoSeis: default (UI «Manual», alias manual), acceptEdits, plan, auto, dontAsk, bypassPermissionsv2.1.200 (alias)AP-PERM-11https://code.claude.com/docs/en/permission-modes
Un settings de mayor precedencia reemplaza la lista de permisosLas reglas allow/ask/deny se fusionan entre todos los scopesAP-PERM-01https://code.claude.com/docs/en/permissions
La regla más específica gana; se pueden hacer excepciones dentro de un denyOrden deny → ask → allow, primera coincidencia decideAP-PERM-02https://code.claude.com/docs/en/permissions
Write(path) restringe escriturasSolo Edit(path) y Read(path) se evalúan; Write/NotebookEdit/Glob nunca coincidenv2.1.210AP-PERM-03https://code.claude.com/docs/en/permissions
Un deny de Read no afectaba a EditUn deny de Read bloquea también Edit sobre esa rutav2.1.208AP-PERM-03https://code.claude.com/docs/en/permissions
/path es la raíz del filesystem/path es relativa al origen del settings; la absoluta es //pathAP-PERM-04https://code.claude.com/docs/en/permissions
El if de un hook con Edit(src/**) coincidía con un src a cualquier profundidadCubre 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-13https://code.claude.com/docs/en/hooks#common-fields
defaultMode: "auto" valía desde cualquier settingsSe ignora si viene de settings de proyecto o localv2.1.142AP-PERM-11https://code.claude.com/docs/en/permission-modes
«Yes, don’t ask again» se guardaba en el directorio de inicioSe guarda en .claude/settings.local.json de la raíz del repo git y se carga desde ahív2.1.211AP-SET-05https://code.claude.com/docs/en/permissions
.claude/settings.local.json tratado como suministrado por el repoRestaurado su tratamiento como archivo local del usuariov2.1.196→v2.1.200AP-SET-05https://code.claude.com/docs/en/permissions
«Safe YOLO mode» con --dangerously-skip-permissions era la recomendación para autonomíaAuto mode + allowlists vía /permissions + sandbox OS-level vía /sandboxAP-PERM-14, P-SEC-01https://code.claude.com/docs/en/best-practices
CLAUDE_CODE_ENABLE_AUTO_MODE=1 era obligatoria en todos los proveedoresSolo 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.207AP-MOD-06https://code.claude.com/docs/en/permission-modes
El clasificador de auto mode corría en el modelo de la sesiónCorre en Sonnet 5 y sus tokens se facturanv2.1.210P-SEC-01https://code.claude.com/docs/en/permission-modes
El sandbox protegía credenciales y fallaba cerradoLee todo el equipo por defecto y falla abierto salvo failIfUnavailable: true; hace falta sandbox.credentialsv2.1.187 / v2.1.199 (mask)AP-SBX-01, AP-SBX-04https://code.claude.com/docs/en/sandboxing
La memoria era solo CLAUDE.mdExisten además auto memory (~/.claude/projects/<p>/memory/) y .claude/rules/*.md con paths:AP-MEM-06, AP-MEM-09https://code.claude.com/docs/en/memory
Claude Code leía AGENTS.md automáticamenteNo lo carga en runtime: hace falta @AGENTS.md o symlinkAP-MEM-03https://code.claude.com/docs/en/memory
Los hooks tenían 9 eventos y solo handler command30 eventos y cinco tipos de handler (command, http, mcp_tool, prompt, agent)AP-HOOK-03https://code.claude.com/docs/en/hooks
exit 1 bloqueaba; exit 2 con JSON malformado dejaba pasarSolo exit 2 bloquea; exit 2 con JSON inválido bloquea vía stderrv2.1.214AP-HOOK-01https://code.claude.com/docs/en/hooks#exit-code-output
PreToolUse se controlaba con decision: "approve"/"block" top-levelhookSpecificOutput.permissionDecision con allow | deny | ask | deferAP-HOOK-04https://code.claude.com/docs/en/hooks#pretooluse
Los slash commands eran un sistema aparteComandos y skills fusionados; ante colisión gana la skillAP-SKL-06https://code.claude.com/docs/en/slash-commands
allowed-tools restringía; $1 era el primer argumentoallowed-tools pre-aprueba (restringe disallowed-tools); $N es base 0AP-SKL-01, AP-SKL-02https://code.claude.com/docs/en/skills#frontmatter-reference
/agents abría un wizard; los subagentes corrían en primer plano y no podían anidarSin 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.212AP-AGT-04https://code.claude.com/docs/en/sub-agents
Un subagente con tools recortado era una frontera de seguridadNo lo es: mismo proceso y misma configuración de sandboxAP-AGT-01https://code.claude.com/docs/en/sandboxing
/output-style cambiaba el estilo de salidaDeprecado en v2.1.73 y eliminado en v2.1.91: se usa /config o el campo outputStylev2.1.91https://code.claude.com/docs/en/output-styles
Dos transportes MCP prácticos y había que limitar servidores por tokensCuatro transportes (sse deprecado) y tool search activo por defecto; el riesgo es superficie de ataqueAP-MCP-02, AP-MCP-03https://code.claude.com/docs/en/mcp
OAuth de MCP solo desde /mcp interactivoclaude mcp login <name> desde la shell, con --no-browser y --callback-portv2.1.186 / v2.1.191AP-MCP-05https://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-07https://code.claude.com/docs/en/mcp
Los IDs de modelo llevaban sufijo de fechaDesde la generación 4.6 no lo llevan (siguen siendo snapshots pinneados)AP-MOD-01https://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-02https://platform.claude.com/docs/en/about-claude/models/overview
temperature/top_p/top_k y prefill del turno assistant finalDevuelven 400; se usa prompting y structured outputsAP-MOD-03https://platform.claude.com/docs/en/about-claude/models/overview
effort requería cabecera beta y llegaba hasta highGA sin beta, en output_config.effort, hasta max; xhigh recomendado para codingAP-MOD-04https://platform.claude.com/docs/en/about-claude/models/overview
-p conservaba las barreras de confianza de una sesión interactivaLa verificación de trust queda desactivada con -p; existe --bare (futuro default)AP-MOD-05, P-OPS-01https://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