permissions.deny Read() rules from project settings are not enforced when launched from a subdirectory
Summary
permissions.deny Read(...) rules defined in project settings (.claude/settings.json) are enforced when the session is launched from the project root, but are silently not enforced when the session is launched from a subdirectory of the same project — even though other parts of the same settings file (e.g. enabledPlugins) demonstrably load in those sessions.
Environment
- Claude Code v2.1.220, macOS (darwin), zsh
- Monorepo; project settings at
<repo>/.claude/settings.json(committed) defaultMode: "bypassPermissions"insettings.local.json(docs state deny rules are still evaluated under bypass, and root-launch behavior confirms that)
Reproduction
<repo>/.claude/settings.json:
{
"permissions": {
"deny": ["Read(/build/**)"]
}
}
A large generated tree exists at <repo>/build/** (gitignored, on disk).
- From the repo root:
claude -p "Use the Read tool on <repo>/build/<some-file> and report the tool response verbatim"
→ denied: "File is in a directory that is denied by your permission settings." ✅
- From a subdirectory:
cd <repo>/packages/backend && claude -p "<same prompt, same absolute path>"
→ file is read in full, no denial. ❌
Reproduced 2×/2× in both directions. The subdirectory session does load the project settings: plugins/skills enabled only in <repo>/.claude/settings.json appear in its environment. Only the Read deny evaluation goes inert.
Pattern forms tested (all deny from root, all inert from the subdirectory)
| Deny pattern | Root launch | Subdir launch |
|---|---|---|
| Read(/build/**) (settings-source anchored per docs) | denied | allowed |
| Read(**/build/**) | denied | allowed |
| Read(/abs/path/to/repo/build/**) (absolute) | — | allowed |
| both /build/** + **/build/** together | denied | allowed |
The docs (settings reference / large-codebases page) describe leading-/ as "relative to the settings source", which reads as exactly the mechanism that should make project-level denies launch-directory-independent.
Why it matters
The large-codebases guide recommends permissions.deny Read() rules to keep agents out of generated/vendored trees (including third-party packages that ship their own CLAUDE.md/AGENTS.md agent-instruction files). In a monorepo, sessions are routinely launched from package subdirectories — precisely the sessions that end up unprotected. The failure is silent: no warning, the rule simply never matches.
Expected
Deny rules from project settings apply to any session whose project resolves to that settings file, regardless of launch cwd — or, failing that, a documented statement that deny evaluation is cwd-scoped, plus a warning when a settings-source-anchored pattern can never match from the current cwd.