anthropics / anthropics/claude-code

[FEATURE] `.agentsignore`: gitignore-syntax file for paths agents must never access

Abierto
#92,816 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:permissions area:security enhancement
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

### Preflight Checklist

- [x] I have searched [existing requests](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20label%3Aenhancement) and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)

### Problem Statement

Claude Code can already express "never touch this" through `permissions.deny`, and the [permissions docs](https://code.claude.com/docs/en/permissions) confirm `Read` and `Edit` rules use gitignore pattern syntax, resolve `//`, `~/`, `/`, and `./` anchors, and block in every mode including `bypassPermissions`. The capability is real. What is missing is a way to *state* it as an ignore file rather than as Claude-specific JSON permission rules.

Four concrete costs of the settings-only form:

**1. Every path must be written twice.** `Read()` and `Edit()` are separate rule namespaces, so protecting one directory means two entries, and forgetting the second silently leaves writes allowed:

```json
{
"permissions": {
"deny": [
"Read(~/vaults/personal/**)",
"Edit(~/vaults/personal/**)",
"Read(//Users/me/clients/acme/**)",
"Edit(//Users/me/clients/acme/**)"
]
}
}
```

**2. No re-inclusion.** gitignore's defining feature is `!`, and it is exactly what carve-outs need. The docs state a deny rule "can't carry allowlist exceptions," and a narrower `allow` rule does not override a broader `deny`. So "ignore this whole tree except one subdirectory" is not expressible at all:

```gitignore
vaults/personal/
!vaults/personal/work-notes/ # no equivalent today
```

**3. The `/path` anchor is a documented footgun.** A `/`-anchored rule resolves against *the settings source that defines it*, so `Read(/secrets/**)` written in `~/.claude/settings.json` blocks `~/.claude/secrets/**` — not the `secrets/` directory in any project. The docs call this out explicitly, which is a sign the anchoring model is doing more work than an ignore file would need to.

**4. Patterns cannot be colocated or checked in as an ignore file.** They live only in settings.json layers, so the rules travel with the settings file rather than with the tree they describe, and there is no per-directory form the way `.gitignore` has one.

Underneath all four: `.gitignore` answers "what should git not track," which is a genuinely different question from "what should an agent never read." Files are routinely one and not the other in both directions — a gitignored `docs/plans/` the agent should read freely, a checked-in fixture or vendored blob it should skip. There is no file today that answers the second question, and `.claudeignore` — which the model itself sometimes suggests — is not a real feature and appears nowhere in the documentation.

### Proposed Solution

Support an `.agentsignore` file whose contents are gitignore syntax, interpreted as "do not read, search, edit, or write these paths."

**Semantics**

- Same pattern grammar as `.gitignore`, including `!` re-inclusion and last-match-wins ordering. Reusing the grammar wholesale is the point: no new syntax to learn, no per-tool wrapping.
- One entry governs all file access — `Read`, `Edit`, `Write`, `Glob`, `Grep`, `@`-mention pickers, and the Bash file commands `permissions.deny` already recognizes.
- Anchoring follows gitignore's own rule: patterns resolve relative to the directory containing the file. No settings-source indirection.

**Discovery**

- Project: `.agentsignore` at the working-directory root, and in subdirectories, matching how `.gitignore` nests.
- Global: `~/.agentsignore`, applying to every session regardless of cwd — the cross-project layer, expressed as patterns instead of `//` absolute paths.

**Precedence**

Treat it as equivalent in force to a `permissions.deny` rule: it blocks in every permission mode including `bypassPermissions`, and it composes with existing deny rules rather than replacing them. `!` re-inclusion should carve out only within `.agentsignore` itself, so it can never widen access past an explicit `permissions.deny` rule or a managed policy.

**Why this name.** `.gitignore` is scoped to version control and reusing it for agent access control conflates two orthogonal concerns. `AGENTS.md` has become the cross-tool convention for agent instructions; `.agentsignore` extends that convention to access scope, so one file serves every agent CLI a team runs instead of each shipping its own dotfile. A Claude-specific `.claudeignore` would work mechanically but would guarantee that a team running more than one agent maintains N parallel files.

### Alternative Solutions

**`permissions.deny` in `~/.claude/settings.json`** — the current best answer, and what I use. It covers the core need genuinely well: gitignore pattern syntax, cross-project reach via `//` and `~/` anchors, enforcement in every mode. Its limits are the four in the problem statement: duplicate `Read()`/`Edit()` entries, no `!` re-inclusion, settings-source anchoring for `/path`, and no colocated or per-directory file form.

**Sandboxing** — [documented](https://code.claude.com/docs/en/sandboxing) as the OS-level answer, and the only thing that stops arbitrary subprocesses from reading a path. It is a different tool for a different job: it enforces a boundary, it does not express a curated ignore list, and it is coarse where this request wants pattern granularity. `.agentsignore` would not replace it.

**`.gitignore` as a proxy** — rejected. The two questions genuinely diverge, in both directions.

**Prior art in this repository** (none currently open, and none covering an ignore-file form):

- #25606 — `deniedPaths` array in `~/.claude/settings.json`. Closest existing request; closed as inactive after the author pushed back on a duplicate-bot flag, not resolved on merits.
- #63990, #65812, #63967 — `.claudeignore` proposals, all closed. #65812 makes the same `.gitignore`-is-not-access-control argument, scoped to a project-level Claude-specific file.
- #92643 — open, but the inverse model: a default-deny *allowlist* (`restrictFileToolsToDirectories`) for Read/Write/Edit. It reports the same underlying constraint from the other direction — a broad `Deny` defeats a narrower nested `Allow`, so exceptions are inexpressible. `!` support in an ignore file addresses that constraint within the denylist model.

### Priority

Medium - Would be very helpful

### Feature Category

Configuration and settings

### Use Case Example

I run several agent CLIs in the same working tree, and my home directory holds material no agent should read: a personal knowledge vault, client work under NDA in sibling directories, and local credential stores.

**Today**, the global layer is `~/.claude/settings.json`:

```json
{
"permissions": {
"deny": [
"Read(~/vaults/personal/**)",
"Edit(~/vaults/personal/**)",
"Read(~/clients/**)",
"Edit(~/clients/**)",
"Read(//**/*.pem)",
"Edit(//**/*.pem)"
]
}
}
```

This works. Three observations from maintaining it:

1. Six entries for three rules, and the failure mode of dropping an `Edit()` line is silent — the path stays writable.
2. I cannot express the carve-out I actually want. `~/vaults/personal/` holds one subdirectory of work notes I *do* want the agent to read. There is no way to write that, so I either expose the whole vault or enumerate every sibling to exclude — and re-enumerate whenever I add one.
3. The rules are Claude-specific, so the same policy is restated in each other agent's config format, and the three drift.

**With `.agentsignore`**, `~/.agentsignore` — patterns anchored at the file's own directory, so home-relative here:

```gitignore
# Personal vault, except the work notes
vaults/personal/
!vaults/personal/work-notes/

# Client work under NDA
clients/

# Key material anywhere below home
**/*.pem
```

One file, one grammar, and the carve-out on the third line is the case that has no expression today.

**Per-project**, a checked-in `.agentsignore` at a repository root:

```gitignore
# Vendored — large, and never the thing being changed
vendor/
third_party/

# Recorded fixtures: real payloads, scrubbed but still noisy
tests/fixtures/recorded/
!tests/fixtures/recorded/README.md
```

Every one of these is tracked by git, so `.gitignore` cannot express any of it, and today the whole set has to be restated as `Read()`/`Edit()` pairs in `.claude/settings.json`.

### Additional Context

**Interaction with existing configuration.** `.agentsignore` should compose with `permissions.deny` rather than replace it, and never widen access: a `!` line should carve out only within `.agentsignore`'s own matches, so an explicit `permissions.deny` rule or a managed-settings policy still wins. Settings-based rules stay the right home for tool-scoped rules like `Bash(...)`; this file is only about file paths.

**Scope boundary.** Same enforcement surface `permissions.deny` documents today — built-in file tools, recognized Bash file commands, redirection targets. Arbitrary subprocesses that open files themselves remain the [sandbox](https://code.claude.com/docs/en/sandboxing)'s domain. This request does not claim to close that gap.

**Incremental path.** If a new file is more than is wanted, the single highest-value piece is `!` re-inclusion in existing `permissions.deny` patterns. That alone makes "ignore this tree except one subdirectory" expressible, which is the one thing in this request that has no workaround at all — the rest is ergonomics and portability.

**Prior art.** `.gitignore`, `.dockerignore`, `.npmignore`, `.eslintignore`, and `.prettierignore` all establish that a plaintext pattern file colocated with the tree it describes is the ergonomic shape for this problem. `AGENTS.md` establishes the cross-tool naming convention this proposal follows.

**Documentation note, tangential but related.** `.claudeignore` does not appear anywhere in the documentation and does not exist, yet the model recommends it to users unprompted — #16704 documents that confusion, and it recurs across #36163, #51105, #56997, and #86196, where users file bugs against a file that was never a feature. Whatever the outcome here, a stated position on ignore files would stop that class of report.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

Start with the permissions documentation and the existing permissions.deny configuration in ~/.claude/settings.json, then trace how file access is enforced across Read, Edit, Write, Glob, Grep, @-mention pickers, and recognized Bash file commands. Define discovery and precedence for project and global .agentsignore files, including gitignore ordering and ! re-inclusion. Done means the documented access surfaces honor the file while explicit deny and managed policies still win; no repository tests or implementation files are named in the issue.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
git
Área
cli, security
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.