anthropics / anthropics/claude-code

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

Offen
#92,816 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area:permissions area:security enhancement
Vorherrschende Sprache
Python
Sterne
145k
Forks
23.1k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

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

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Rechercherichtung

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.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
git
Bereich
cli, security
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Aktiv
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.