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

Status Open
Maintainer reply None cached
Activity 0 comments · opened Sep 8, 2026

Preflight Checklist

  • [x] I have searched existing requests 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 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:

{
  "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:

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 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:

{
  "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:

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

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

View original on GitHub ↗