Guía de configuración

Nivel por nivel: qué permite y qué no, con los errores reales de una configuración ingenua, su causa y su arreglo.

SETTINGS.md/515 líneas

metodologia/templates/aislamiento/SETTINGS.md
# Aislamiento de la sesión — `settings.json` + `settings.local.json`

Plantilla y explicación del aislamiento de una sesión de Claude Code para un
proyecto Argui. Documenta, nivel por nivel, qué permite y qué **no**, y —muy
importante— los **errores reales** que aparecen si se configura de forma ingenua,
con su causa y su arreglo.

> Probado en Linux (Fedora). Algunas rutas y, sobre todo, el **límite de globs**
> (sección 4) son específicos de Linux; en macOS los paths de sistema cambian.

## Las dos capas de control

Hay **dos capas independientes**, con mecanismos y reglas de precedencia distintas.
Confundirlas es la causa de casi todos los problemas:

| Capa | Qué gobierna | Nivel | Precedencia |
|------|--------------|-------|-------------|
| `sandbox` | Ejecución de **comandos de shell** (tool Bash) | Kernel / SO (bubblewrap) | El `allow` más específico **recorta** el `deny` |
| `permissions` | **Cada tool call** (Read / Edit / Write / Bash / MCP) | Aplicación (Claude Code) | `deny` **siempre** gana a `allow` |

Regla mental:
- **`sandbox`** = barrera dura del SO alrededor de los *comandos*. **Sí** puede
  expresar "todo el disco menos esta carpeta" (allow recorta el deny).
- **`permissions`** = portero de la aplicación alrededor de *cada acción de tool*.
  **No** puede expresar "todo menos el proyecto" (porque `deny` gana a `allow`);
  aquí se permite el proyecto y se vetan rutas sensibles concretas.
- Las tools **Read/Edit/Write NO pasan por el `sandbox`**: solo las gobierna
  `permissions`. Los comandos de shell pasan por **las dos**.

---

## Los dos archivos: qué va versionado y qué no

La configuración se parte en dos porque tiene dos naturalezas distintas:

| Archivo | Qué lleva | ¿Se versiona? |
|---|---|---|
| `.claude/settings.json` | La **línea base de seguridad del proyecto**: banderas del sandbox, rutas de sistema portables, los `deny` de sistema/secretos/`.env` y el autobloqueo | **Sí** — quien clona hereda el aislamiento |
| `.claude/settings.local.json` | Lo **específico de la máquina**: toolchains con ruta absoluta, `additionalDirectories`, dominios de red propios | **No** — va al `.gitignore` |

> **Las listas se suman entre archivos.** Cuando una misma clave de lista (`permissions.allow`,
> `permissions.deny`, `sandbox.filesystem.allowRead`, …) aparece en varios archivos, Claude Code
> **combina** las listas en vez de quedarse con una: el archivo local **agrega** rutas sin repetir
> la base. Lo que no cambia es la precedencia dentro de `permissions`: **`deny` gana a `allow`**,
> venga del archivo que venga. Verifica el resultado con **`/sandbox`**. Referencia:
> <https://code.claude.com/docs/en/settings.md> → *Settings precedence*.

---

## Línea base — `.claude/settings.json` (versionada)

```json
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["/**"],
      "allowRead": [
        ".",
        "/usr", "/bin", "/sbin", "/lib", "/lib64", "/opt",
        "/etc", "/proc", "/dev", "/tmp", "/var/tmp"
      ],
      "allowWrite": [".", "/tmp"]
    },
    "network": { "allowedDomains": [] }
  },
  "permissions": {
    "defaultMode": "default",
    "deny": [
      "Read(//etc/**)", "Edit(//etc/**)", "Write(//etc/**)",
      "Read(//root/**)", "Edit(//root/**)", "Write(//root/**)",
      "Read(//boot/**)", "Edit(//boot/**)", "Write(//boot/**)",
      "Read(//sys/**)", "Edit(//sys/**)", "Write(//sys/**)",
      "Read(//proc/**)", "Edit(//proc/**)", "Write(//proc/**)",
      "Read(//dev/**)", "Edit(//dev/**)", "Write(//dev/**)",
      "Read(//run/**)", "Edit(//run/**)", "Write(//run/**)",
      "Read(//var/log/**)", "Edit(//var/log/**)", "Write(//var/log/**)",
      "Read(//var/lib/**)", "Edit(//var/lib/**)", "Write(//var/lib/**)",
      "Read(//var/backups/**)", "Edit(//var/backups/**)", "Write(//var/backups/**)",
      "Read(//var/spool/**)", "Edit(//var/spool/**)", "Write(//var/spool/**)",
      "Read(//usr/local/etc/**)", "Edit(//usr/local/etc/**)", "Write(//usr/local/etc/**)",

      "Read(~/.ssh/**)", "Edit(~/.ssh/**)", "Write(~/.ssh/**)",
      "Read(~/.gnupg/**)", "Edit(~/.gnupg/**)", "Write(~/.gnupg/**)",
      "Read(~/.aws/**)", "Edit(~/.aws/**)", "Write(~/.aws/**)",
      "Read(~/.azure/**)", "Edit(~/.azure/**)", "Write(~/.azure/**)",
      "Read(~/.kube/**)", "Edit(~/.kube/**)", "Write(~/.kube/**)",
      "Read(~/.docker/**)", "Edit(~/.docker/**)", "Write(~/.docker/**)",
      "Read(~/.config/**)", "Edit(~/.config/**)", "Write(~/.config/**)",
      "Read(~/.claude/**)", "Edit(~/.claude/**)", "Write(~/.claude/**)",
      "Read(~/.password-store/**)", "Edit(~/.password-store/**)", "Write(~/.password-store/**)",
      "Read(~/.mozilla/**)", "Edit(~/.mozilla/**)", "Write(~/.mozilla/**)",
      "Read(~/.local/share/keyrings/**)", "Edit(~/.local/share/keyrings/**)", "Write(~/.local/share/keyrings/**)",

      "Read(~/.netrc)", "Edit(~/.netrc)", "Write(~/.netrc)",
      "Read(~/.pgpass)", "Edit(~/.pgpass)", "Write(~/.pgpass)",
      "Read(~/.my.cnf)", "Edit(~/.my.cnf)", "Write(~/.my.cnf)",
      "Read(~/.npmrc)", "Edit(~/.npmrc)", "Write(~/.npmrc)",
      "Read(~/.pypirc)", "Edit(~/.pypirc)", "Write(~/.pypirc)",
      "Read(~/.git-credentials)", "Edit(~/.git-credentials)", "Write(~/.git-credentials)",
      "Read(~/.bash_history)", "Edit(~/.bash_history)", "Write(~/.bash_history)",
      "Read(~/.zsh_history)", "Edit(~/.zsh_history)", "Write(~/.zsh_history)",

      "Read(.env)", "Edit(.env)", "Write(.env)",
      "Read(.env.local)", "Edit(.env.local)", "Write(.env.local)",
      "Read(.env.production)", "Edit(.env.production)", "Write(.env.production)",

      "Read(frontend/.env)", "Edit(frontend/.env)", "Write(frontend/.env)",
      "Read(frontend/.env.local)", "Edit(frontend/.env.local)", "Write(frontend/.env.local)",
      "Read(frontend/.env.production)", "Edit(frontend/.env.production)", "Write(frontend/.env.production)",

      "Read(backend/.env)", "Edit(backend/.env)", "Write(backend/.env)",
      "Read(backend/.env.local)", "Edit(backend/.env.local)", "Write(backend/.env.local)",
      "Read(backend/.env.production)", "Edit(backend/.env.production)", "Write(backend/.env.production)",

      "Edit(.claude/settings.json)", "Write(.claude/settings.json)",
      "Edit(.claude/settings.local.json)", "Write(.claude/settings.local.json)"
    ],
    "allow": [
      "Read(./**)", "Edit(./**)", "Write(./**)"
    ]
  }
}
```

---

## Específico de la máquina — `.claude/settings.local.json` (no versionada)

Arranca vacío y crece solo con lo que tu equipo necesite:

```json
{
  "sandbox": {
    "filesystem": {
      "allowRead": [],
      "allowWrite": []
    },
    "network": { "allowedDomains": [] }
  },
  "permissions": {
    "additionalDirectories": []
  }
}
```

Ejemplo con contenido —toolchain bajo `$HOME`, su caché de escritura, una carpeta externa de
consulta y un registro de paquetes—:

```json
{
  "sandbox": {
    "filesystem": {
      "allowRead": ["/home/<usuario>/.nvm"],
      "allowWrite": ["/home/<usuario>/.npm"]
    },
    "network": { "allowedDomains": ["registry.npmjs.org"] }
  },
  "permissions": {
    "additionalDirectories": ["~/workspace/metodologia-argui"]
  }
}
```

Reglas de reparto, para no dudar cada vez:

- **Ruta absoluta con tu usuario dentro** (`/home/…`, `~/.nvm`, `~/workspace/…`) → local.
- **Ruta de sistema portable** (`/usr`, `/opt`, `/etc`, `/tmp`) → base.
- **Política que debe cumplir todo el equipo** (qué se deniega, qué dominios abre el proyecto) → base.
- **Conveniencia tuya en esta máquina** → local.

> `/opt` va en la **base**: es donde viven los toolchains instalados fuera de `$HOME` y la ruta es
> igual en cualquier máquina. Con lectura basta para ejecutar sus binarios; si alguna herramienta
> además escribe ahí, esa línea sí es local.

---

## 1. Capa `sandbox` (comandos de shell)

### Banderas
- **`enabled: true`** — sandbox activo; todos los comandos se ejecutan confinados por el SO.
- **`failIfUnavailable: true`** — si el sandbox no arranca, el comando **falla** en vez de correr sin protección.
- **`allowUnsandboxedCommands: false`** — **prohíbe** ejecutar comandos fuera del sandbox. Sin vía de escape.

### `filesystem.denyRead` / `allowRead`
- `denyRead: ["/**"]` = deniega leer **todo** el disco.
- `allowRead` recorta las excepciones (aquí gana el **más específico**): el proyecto (`.`) **y** las rutas de runtime del sistema, `/opt` incluido (toolchains instalados fuera de `$HOME`).

> ⚠️ **Error clásico #1 — bash no arranca.** Si pones solo `allowRead: ["."]`, el
> primer comando falla con:
> ```
> bwrap: execvp /bin/bash: No such file or directory
> ```
> **No** es que falte bash: el sandbox oculta `/usr/bin/bash` y su cargador dinámico
> (`/lib64/ld-linux-*.so`, `libc`). Sin acceso de lectura al binario **ni a sus
> librerías**, `execvp` devuelve `ENOENT`. Por eso `allowRead` **debe** incluir
> `/usr`, `/bin`, `/sbin`, `/lib`, `/lib64` (y normalmente `/etc`, `/proc`, `/dev`,
> `/tmp`, `/var/tmp`). Tensión de fondo: *no se puede* tener "los comandos solo leen
> el proyecto" **y** "los comandos funcionan" — ejecutar cualquier cosa exige leer el
> shell y sus librerías del sistema.

> ⚠️ **Error clásico #3 — un binario en `$HOME` sale como "command not found".**
> Si el toolchain vive bajo `$HOME` (nvm, pyenv, cargo, rbenv, …), estar en el `PATH`
> **no basta**: el sandbox tapa la ruta y el binario aparece como inexistente aunque
> `echo $PATH` lo muestre. Ejemplo real: `node`/`npm`/`npx` y el CLI de `openspec` en
> `/home/<usuario>/.nvm/versions/node/vX.Y.Z/bin` fallan con *command not found* hasta
> que se añade la raíz del toolchain (`/home/<usuario>/.nvm`) a `allowRead`.
> - Con **solo lectura** basta para *ejecutar* binarios (correr = leer + ejecutar).
> - `npm install` / `pip install` además necesitan **escritura**: en su caché
>   (`~/.npm`, `~/.cache/pip`, …) — hay que añadirla a `allowWrite` — o instalar como
>   dependencia del proyecto (escribe en `.`, ya permitido).
> - Usa la **ruta absoluta** (`/home/<usuario>/.nvm`), no `~`: el sandbox aplica rutas
>   literales (ver §4) y no expande `~` con fiabilidad.
> - Esa línea va en **`settings.local.json`**: lleva tu nombre de usuario y no sirve en la máquina
>   de nadie más. Si el toolchain está en `/opt`, ya lo cubre la línea base.

### `filesystem.allowWrite`
- Por defecto solo son escribibles el cwd y el temporal de sesión. Al definir un
  bloque `filesystem` propio, ese default se pierde y hay que declararlo.

> ⚠️ **Error clásico #2 — cada comando termina con `exit 1`.** Síntoma:
> ```
> /bin/bash: .../cwd-XXXX: Sistema de ficheros de sólo lectura
> ```
> El wrapper de la tool Bash escribe un fichero de seguimiento del directorio actual
> en `$TMPDIR` (`/tmp/claude-<uid>`). Un `denyRead: ["/**"]` fuerza a bubblewrap a
> remontar `/tmp` como **solo lectura**, tapando ese temporal. Arreglo: añadir `/tmp`
> a `allowWrite`. (Añadir solo `/tmp/claude-<uid>` **no** basta: la capa RO de `/tmp`
> lo sombrea; hay que abrir `/tmp` entero.)

### `network.allowedDomains`
- Lista blanca de red **para los comandos** (modelo *default-deny*). `[]` = **sin red**.
- **NO** afecta a: las llamadas del modelo Claude (las hace el harness) ni a
  `WebFetch`/`WebSearch` (van por la infraestructura de Anthropic). Por eso el agente
  sigue funcionando con la red de comandos cerrada.
- Si un `npm/pip install` necesita red, añade hosts concretos
  (`registry.npmjs.org`, `pypi.org`, `files.pythonhosted.org`, …) — o instala fuera del sandbox.

---

## 2. Capa `permissions` (tool calls)

Precedencia: **`deny` gana siempre a `allow`.** El proyecto se habilita con `allow`
y no aparece en ningún `deny`; toda ruta en `deny` queda bloqueada aunque el `allow`
la cubriera.

### Anclaje de rutas: `//`, `/`, `~/` y relativas

El prefijo de la ruta decide a qué punto del disco apunta la regla:

| Prefijo | Ancla en | Ejemplo | Qué cubre de verdad |
|---|---|---|---|
| `//` (doble barra) | Raíz del **sistema de archivos** | `Read(//etc/**)` | `/etc` en el disco |
| `/` (una barra) | Raíz del **proyecto** (el origen de la regla) | `Read(/etc/**)` | `<proyecto>/etc` |
| `~/` | **Home** del usuario | `Read(~/.ssh/**)` | `$HOME/.ssh` |
| Sin prefijo | Relativa al origen de la regla | `Read(.env)` | `<proyecto>/.env` |

De la doc oficial (<https://code.claude.com/docs/en/permissions>):

> Use `//path` for an absolute filesystem path: a deny rule of `Edit(//secrets/**)` blocks
> writes anywhere under `/secrets` on disk. In contrast, with a single leading slash,
> `Edit(/secrets/**)` anchors at the rule's source instead.

Por eso los `deny` de sistema de la línea base llevan **doble barra**: `Read(//etc/**)`,
`Read(//root/**)`, `Read(//var/log/**)`, … Con una sola barra apuntan a `<proyecto>/etc`,
`<proyecto>/root`, … —carpetas que en un proyecto normal ni existen— y el disco real queda
abierto. En la línea base son 36 reglas —12 familias de rutas de sistema × Read/Edit/Write—
las que dependen de este prefijo.

**Verificado empíricamente.** Con `Read(/etc/**)` activa, leer `/etc/os-release` funciona
sin aviso y `/sandbox` (pestaña *Config*) muestra la regla resuelta contra `<proyecto>/etc`.
Con `Read(//etc/**)` y la sesión reiniciada, `/etc/hostname` queda denegado.

Las reglas con `~/` y las relativas de la línea base (`.env`, `frontend/.env`, el autobloqueo
de `settings*.json`) ya anclan donde deben: se escriben tal cual.

> Los patrones del `sandbox` (`filesystem.allowRead`, `denyRead`, …) usan rutas absolutas
> normales, de una sola barra. El anclaje `//` es de la capa `permissions`.

### `allow`
```json
"allow": ["Read(./**)", "Edit(./**)", "Write(./**)"]
```
Leer/editar/crear dentro del proyecto sin preguntar.

### `deny` — rutas vetadas (leer, editar y escribir)
- **Sistema:** `/etc` (incluye `passwd`, `shadow`, `sudoers`), `/root`, `/boot`,
  `/sys`, `/proc`, `/dev`, `/run`, `/var/{log,lib,backups,spool}`, `/usr/local/etc`.
- **Secretos de usuario:** `~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.azure`, `~/.kube`,
  `~/.docker`, `~/.config`, `~/.claude`, `~/.password-store`, `~/.local/share/keyrings`, `~/.mozilla`.
- **Ficheros de credenciales:** `~/.netrc`, `~/.pgpass`, `~/.my.cnf`, `~/.npmrc`,
  `~/.pypirc`, `~/.git-credentials`, `~/.bash_history`, `~/.zsh_history`.
- **Entorno del proyecto:** `.env`, `.env.local`, `.env.production` — ver sección 4.

> ⚠️ **Efecto colateral de `~/.claude`.** Ese `deny` (necesario para proteger config
> global, `.credentials.json`, etc.) **también tapa la memoria persistente** del propio
> Claude Code (`~/.claude/projects/<slug>/memory/`): el agente no podrá guardar memoria
> entre sesiones y ese directorio ni siquiera se crea. Como `deny` gana a `allow`, **no**
> se puede reabrir solo esa subcarpeta con un `allow`; habría que **estrechar el `deny`**
> enumerando lo sensible (`~/.claude/*.json`, `~/.claude/.credentials.json`, `plugins/`,
> `agents/`, `hooks/`, `projects/**/*.jsonl`, …) — un foot-gun de seguridad (si olvidas
> un fichero, abres un hueco justo donde vive el token).
> **Recomendación Argui:** no pelear el aislamiento. Usa un **documento vivo revisado por
> el humano** (`CLAUDE.md`, sección *Notas operativas*) como memoria entre sesiones —
> coherente con "el humano controla lo que entra al historial".

> ⚠️ **Segundo efecto colateral de `~/.claude`: `WebFetch` se queda mudo.** Las salidas
> grandes de `WebFetch` se guardan en `~/.claude/projects/<slug>/.../tool-results/`, dentro
> del árbol denegado: el agente lanza la petición pero **no puede leer el resultado**.
> Salida: usar **`WebSearch`**, cuyos fragmentos vuelven inline en la respuesta y no
> tocan disco.

### `additionalDirectories`
Amplía el "workspace de confianza" a carpetas **fuera** del proyecto, para las tools
de fichero (Read/Edit/Write) **sin preguntar**. Va en **`settings.local.json`** (es una ruta de tu
máquina). Rutas a secas (admite `~`):
```json
"additionalDirectories": ["~/workspace/metodologia-argui"]
```
- Concede lectura **y** escritura vía tools a esa carpeta. **No** anula los `deny`.
- **No** amplía lo que un *comando* del sandbox puede leer: para eso habría que
  añadir la ruta también a `sandbox.filesystem.allowRead`.

> **Una carpeta externa se lee con las tools de fichero, no con comandos.** Es el caso de la
> metodología clonada aparte (Opción 3 del README de Argui) o de un repo hermano: con
> `additionalDirectories` puesto, **Read** la ve; `cat`, `grep`, `ls` o `find` sobre ella fallan
> como si la ruta **no existiera** —el sandbox la tapa y el error engaña, porque el archivo sí
> está—. Deja el sandbox cerrado y consúltala con la herramienta de lectura. Solo si un
> *comando* la necesita de verdad (un script que la recorre), añade además la ruta a
> `sandbox.filesystem.allowRead`, **literal y absoluta** (sin `~` ni globs — ver §4).

---

## 3. Modos de permiso (la zona gris)

El modo se declara con `permissions.defaultMode` y se cambia en caliente desde la pestaña
*Mode* de `/sandbox`. La línea base fija el modo **manual**:

```json
"defaultMode": "default"
```

Con `default`, toda acción que ninguna regla resuelva se le pregunta al humano.

### Qué gobierna el modo

Las reglas `deny` muerden igual en modo automático y en manual: son bloqueo duro. El `allow`
del proyecto tampoco depende del modo. El modo gobierna solo la **zona gris**: lo que no está
ni explícitamente permitido ni denegado —típicamente una ruta fuera del proyecto que ninguna
regla nombra—.

- En **manual**, una lectura fuera del proyecto **pregunta** (verificado).
- En **automático**, la aprueba un clasificador y el humano no llega a verla.

### Las aprobaciones son por sesión y sobreviven al cambio de modo

Lo aprobado en un prompt vale para el resto de la sesión. Pasar de automático a manual
**mantiene vivo** todo lo ya concedido: cambiar de modo no revoca nada. Lo único que limpia
las aprobaciones es **reiniciar la sesión**.

### El prompt concede más de lo que parece

Entre las opciones que ofrece el prompt está *"allow reading from `/etc` during this
session"*: un sí abre el **directorio entero** hasta el reinicio. Con el `deny` bien anclado
(`//etc/**`) esa pregunta ni aparece: el acceso queda cerrado sin decisión que delegar.

### Coste real del modo manual

Como el grueso del trabajo cae dentro del `allow` del proyecto, la fricción diaria es baja.
Donde sí se nota es en **tandas largas de comandos**. La salida ahí es la pestaña *Mode* de
`/sandbox` para esa tarea concreta, o reglas `allow` puntuales en la config. Volver al modo
automático como remedio general devuelve toda la zona gris al clasificador y desactiva la
única barrera que ve el humano.

---

## 4. Límite de globs en Linux (¡importante!)

> **Esto es un problema de la capa `sandbox`, distinto del anclaje de `permissions` (§2).**
> El **anclaje** decide *a qué punto del disco* apunta una regla de `permissions` (`//` =
> raíz del sistema, `/` = raíz del proyecto, `~/` = home) y ahí los globs funcionan. El
> **límite de globs** de abajo decide *qué patrones entiende el sandbox de comandos* en
> Linux: solo rutas literales. Los dos fallan en silencio, y se arreglan distinto: el anclaje,
> cambiando el prefijo; el glob, enumerando la ruta literal.

El **sandbox en Linux solo aplica rutas literales**. Cualquier patrón que cruce
carpetas con comodín se **ignora silenciosamente** (lo avisa `/sandbox`):

| Patrón | ¿Lo aplica el sandbox? |
|--------|------------------------|
| `.env` (literal, raíz) | ✅ Sí |
| `frontend/.env` (literal, subcarpeta) | ✅ Sí |
| `*/.env` (comodín de una carpeta) | ❌ Ignorado |
| `**/.env` (recursivo) | ❌ Ignorado |
| `.env.*` (comodín de nombre) | ⚠️ Evítalo (taparía `.env.example`) |

Consecuencias prácticas:
1. **No hay regla genérica** para `.env` en subcarpetas: hay que **enumerar cada
   carpeta literalmente** (`frontend/.env`, `backend/.env`, …). Cada carpeta nueva
   con secretos = una línea nueva.
2. **No uses `.env.*`** (esto es de la capa `permissions`): como `deny` gana a `allow`,
   taparías `.env.example` (que debe poder leerse/escribirse). Enumera los nombres secretos concretos
   (`.env`, `.env.local`, `.env.production`) y deja fuera `.env.example`/`.sample`/`.template`.
3. Verifica siempre con el comando **`/sandbox`** (pestaña *Config*): muestra las
   restricciones resueltas y un aviso *"patterns ignored"* con lo que no se aplica.

---

## 5. Impedir que el agente modifique su propia configuración

Por defecto el agente puede editar `.claude/settings.local.json` (lo cubre
`Edit(./**)`). Para evitarlo hay dos vías:

### A) Autobloqueo en el propio archivo (simple)
Ya viene en la línea base; si lo quitas y lo quieres de vuelta, añade a `deny` y **reinicia la
sesión**:
```json
"Edit(.claude/settings.local.json)", "Write(.claude/settings.local.json)",
"Edit(.claude/settings.json)", "Write(.claude/settings.json)"
```
Funciona porque, una vez activa, para quitar la regla habría que editar el archivo
que la regla ya prohíbe editar → **se protege a sí misma**. Los comandos de shell ya
no pueden reescribirlo (el sandbox base deniega escribir esos ficheros). La única
"ventana" es antes del reinicio, que es cuando tú la aplicas.
> Requisito: denegar **`Edit` y `Write`** (ambas tools).

### B) Managed settings (blindaje total)
En `/etc/claude-code/managed-settings.json` (requiere `sudo`; el agente no puede
tocar `/etc`). Tienen **precedencia máxima**, no se pueden anular desde el proyecto,
y los `deny` se acumulan entre capas. Sobrevive incluso a que alguien borre la regla
del proyecto. Ideal para fijar toda la línea base de seguridad fuera del alcance del agente.

| | A) Autobloqueo en el archivo | B) Managed settings |
|---|---|---|
| Bloquea al agente | ✅ (tras reiniciar) | ✅ |
| Si borras la regla a mano | Se pierde | Sigue protegido |
| Requiere `sudo` | No | Sí |

> ⚠️ **Orden:** aplica el autobloqueo **al final**. Una vez activo, el agente ya no
> podrá ajustar nada de la config (añadir carpetas `.env`, `additionalDirectories`,
> etc.) — tendrás que editar `settings.local.json` a mano.

---

## 6. Resumen: qué puede y qué no el agente

**SÍ puede:**
- Leer/editar/crear ficheros del proyecto (salvo los `.env` secretos).
- Ejecutar comandos confinados al proyecto, con exit codes limpios.
- Leer las carpetas de `additionalDirectories` vía tools (p. ej. la metodología).
- Razonar, responder y usar `WebSearch` (y `WebFetch` con la limitación de §2: sus salidas
  grandes caen dentro del `deny` de `~/.claude`).

**NO puede:**
- Leer/escribir config del sistema (`/etc`, `/var`, `/root`, …) ni secretos de usuario.
- Leer/escribir los `.env` secretos enumerados.
- Ejecutar comandos fuera del sandbox ni con red saliente.
- Leer el disco fuera del proyecto **vía comandos** (barrera de kernel).
- Modificar `settings.local.json` / `settings.json` (si se aplicó el autobloqueo).

**Zona gris — la resuelve el modo de permiso (§3):**
- Tocar rutas fuera del proyecto que ningún `deny` nombra y que no estén en
  `additionalDirectories` (solo tools de fichero; los comandos ya los bloquea el sandbox).
- Con el modo **manual** de la línea base (`defaultMode: "default"`) esas acciones se le
  **preguntan al humano** (verificado). En modo automático las aprueba un clasificador sin
  que el humano las vea.
- Lo aprobado en el prompt vale para **toda la sesión** —la opción ofrecida concede el
  directorio entero— y **sobrevive al cambio de modo**: pasar de automático a manual no
  revoca nada. Solo lo limpia reiniciar la sesión.
- Lo que está en `deny` nunca llega a preguntarse: es bloqueo duro en cualquier modo.

---

## 7. Troubleshooting rápido

| Síntoma | Causa | Arreglo |
|---------|-------|---------|
| `bwrap: execvp /bin/bash: No such file or directory`, **sin** el runtime declarado | `allowRead` no incluye el runtime del sistema | Añade `/usr`, `/bin`, `/sbin`, `/lib`, `/lib64` a `allowRead` |
| `bwrap: execvp /bin/bash: No such file or directory` **con** `/usr`, `/bin`, `/sbin`, `/lib`, `/lib64` ya en `allowRead` | Sospechado: fallo del entorno o del harness, no de la config. Visto en Fedora 43, kernel 7.1.12, bubblewrap 0.11.0, Claude Code v2.1.258 | Bisecar en tres pasos (abajo) antes de tocar la config |
| Un `deny` de ruta de sistema no protege: el agente lee `/etc/os-release` sin aviso | Regla con **una sola barra** (`Read(/etc/**)`): ancla en `<proyecto>/etc`, no en el disco (§2) | Reescríbela con **doble barra** (`Read(//etc/**)`), reinicia la sesión y confirma en `/sandbox` → *Config* |
| Cada comando termina con `exit 1` y `... Sistema de ficheros de sólo lectura` | `$TMPDIR` (`/tmp`) quedó RO al remontar | Añade `/tmp` a `allowWrite` |
| Un `.env` de subcarpeta **no** está protegido | Usaste `*/` o `**/` (ignorado en Linux) | Enumera la carpeta literal (`carpeta/.env`) |
| `.env.example` bloqueado sin querer | Usaste `.env.*` | Enumera nombres secretos concretos, no comodín |
| No puedo leer una carpeta externa | Falta en `additionalDirectories` | Añádela (ruta a secas, admite `~`) |
| `cat`/`grep` a una carpeta externa: *no existe* (pero `Read` sí la abre) | El sandbox de comandos no cubre `additionalDirectories` | Léela con la herramienta de fichero; si de verdad la necesita un comando, añade la ruta absoluta a `allowRead` |
| `node`/`npm`/binario en `$HOME` = *command not found* pese al `PATH` | Toolchain bajo `$HOME` (nvm/pyenv/…) no está en `allowRead` | Añade su raíz absoluta (p. ej. `/home/<u>/.nvm`) a `allowRead` **del archivo local** |
| Un binario de `/opt` no se ve | Falta `/opt` en `allowRead` | Ya viene en la línea base; si lo quitaste, devuélvelo ahí (ruta portable) |
| Cambias el archivo local y no pasa nada | Las listas se suman, pero hay que reiniciar la sesión | Reinicia y confirma con `/sandbox` (pestaña *Config*) |
| El agente no guarda memoria persistente | `~/.claude/**` en `deny` tapa `~/.claude/.../memory/` | Esperado; usa un doc vivo (`CLAUDE.md` → Notas operativas) como memoria |
| `WebFetch` trae la página pero el agente dice que no puede leer el resultado | El mismo `deny` de `~/.claude/**` tapa `~/.claude/projects/<slug>/.../tool-results/`, donde se guardan las salidas grandes | Usa `WebSearch`: sus fragmentos vuelven inline |
| El agente pudo editar la config | Autobloqueo no aplicado o sin reiniciar | Añade los `deny` de `settings*.json` y reinicia |

### Bisección de `bwrap: execvp` con el runtime ya declarado

Cuando `/usr`, `/bin`, `/sbin`, `/lib` y `/lib64` están en `allowRead` y el error persiste,
la causa está fuera de la config. Estos tres pasos la acotan en minutos:

1. **Repro manual de bubblewrap.** Lanza un `bwrap` a mano con los mismos binds y un
   `echo ok`. Si imprime `ok`, bubblewrap está sano y el `usr-merge` de la distro queda
   descartado.
2. **Proyecto nuevo con config mínima.** Crea una carpeta vacía con un `settings.json`
   reducido y arranca Claude ahí. Si falla igual, el problema es del harness, no de tu
   configuración.
3. **`claude --debug`** para ver la línea de `bwrap` que se ejecuta de verdad, y
   `/sandbox` → pestaña *Dependencies* para el estado de seccomp.

> Verificado que el error aparece con el runtime correctamente declarado (Fedora 43, kernel
> 7.1.12, bubblewrap 0.11.0, Claude Code v2.1.258) y reproducido con un proyecto de config
> mínima. La causa concreta queda como **sospechada**: entorno o harness.

> Herramienta clave de diagnóstico: el comando **`/sandbox`** muestra la config
> resuelta (lecturas/escrituras permitidas y denegadas) y avisa de los globs ignorados.