settings.json in an ancestor directory is silently ignored — hooks and permissions.deny never load, with no warning

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 10, 2026

Summary

A .claude/settings.json that sits in an ancestor directory of the session's project root is silently ignored — including its hooks and permissions.deny. There is no warning, no log line, and no indication anywhere in the session that a settings file exists but is not loaded.

For a security setup this is a quiet failure: I had a full PreToolUse guard suite configured and believed it was enforcing for weeks. It was never invoked.

Layout that triggers it

~/work/                       <- workspace root (NOT a git repo)
  .claude/settings.json       <- hooks + permissions.deny live here
  projects/
    repo-a/                   <- a git repo; sessions are started HERE
    repo-b/

Sessions start inside projects/repo-a, so the project root is repo-a. The settings file two levels up is never consulted. ~/.claude/settings.json (user level) had no hooks key, so nothing loaded at all.

Observed

With hooks configured only in the ancestor file:

$ git clean -nd            # via the Bash tool
Would remove ...           # EXECUTED, exit 0 — no hook ran

$ printf '{"tool_name":"Bash","tool_input":{"command":"git clean -nd"}}' | python3 my_guard.py
ANTI-CLOBBER BLOCK ...     # exit 2 — the guard itself works fine

Same payload: refused when the guard is invoked directly, executed when routed through the tool. Moving the identical hooks block into ~/.claude/settings.json made it fire immediately, on the very next Bash call.

permissions.deny (e.g. Read(//abs/path/**)) is affected the same way — it was inert from the ancestor file and active from the user file.

Why this is worth a signal even if the resolution rule is intended

Not loading an ancestor file may well be correct and documented. The problem is that it is indistinguishable from working. Nothing tells you your guards aren't running, so the failure mode is "I have enforcement" → hours of work under no enforcement at all. Anyone using a workspace-of-repos layout (monorepo-of-repos, nested worktrees) can land here and never know.

Suggestions, roughly in order of cost

  1. On startup, if a .claude/settings.json exists in an ancestor of the project root but is not loaded, emit a warning naming the file and why it was skipped. Cheap, and would have saved this entirely.
  2. Surface loaded hook sources somewhere inspectable — /hooks or /config showing which files contributed and from where.
  3. Optionally consider walking ancestors for settings (as CLAUDE.md discovery already does), or documenting explicitly that it does not.

(1) alone is sufficient. The rule can stay exactly as it is; the silence is the issue.

Environment

  • Claude Code CLI on Linux
  • Project root: a git repo nested two levels below the directory holding .claude/settings.json

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗