[BUG] .claude/settings.json is ignored when starting from a subdirectory — hooks and plugins silently disabled (still repros on 2.1.228; see #10367, #8810)

Status Open
Reported on v2.1.228
Maintainer reply None cached
Activity 1 comment · opened Aug 12, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When Claude Code is started from a subdirectory of a project, <project-root>/.claude/settings.json is never loaded. Every hook and every plugin declared there is inactive for that session, and no warning is printed.

This is not a ${CLAUDE_PROJECT_DIR} expansion problem. With the hook command written as a hard-coded absolute path — no variables at all — the hook still does not fire, so the settings file itself is not being read.

The part that makes this hard to notice: the same session does load <project-root>/CLAUDE.md. I verified this from <project-root>/functions on 2.1.228 by asking for a string that exists only in the root CLAUDE.md, with an explicit instruction not to read files. It answered correctly in ~2s with no tool calls, while the same session had Registered 0 hooks from 1 plugins.

So within a single session, project instructions are resolved by walking up to the project root, but .claude/ is resolved only against the startup directory. The session looks correctly configured and behaves normally, while every guard is off.

For us this silently disables a PreToolUse hook that blocks writes to .env files and requires confirmation for destructive commands. cd app && claude or cd functions && claude is a natural thing to do — several of our repositories keep the application under a subdirectory.

Note: this was reported before and closed without a fix — #10367 (v2.0.27, closed NOT_PLANNED by the autoclose bot) and #8810. #6305 is still open. I am re-filing because it still reproduces on 2.1.228, and because two things are new since those reports: plugins are affected as well as hooks, and the CLAUDE.md asymmetry above gives a concrete inconsistency inside the product rather than just "hooks don't run".

What Should Happen?

Either of these would resolve it:

  1. Resolve .claude/ by walking up to the project root, consistent with how CLAUDE.md is already discovered in the same session.
  1. If changing resolution is too disruptive, at least make the failure visible: when a session starts inside a git repository whose root contains a .claude/settings.json that is not being loaded, print one line saying so at startup. Turning a silent, total failure into a visible one would be enough for us to work with.

git rev-parse --show-toplevel returns the correct project root from all of the directories involved, so the information needed is already available.

Error Messages/Logs

# Real repository with plugins installed at project scope (talktome-app), 2.1.228
# --debug hooks --debug-file, grep 'Registered [0-9]* hooks from [0-9]* plugins'

started from <project-root>        ->  Registered 2 hooks from 3 plugins
started from <project-root>/functions  ->  Registered 0 hooks from 1 plugins

# Same comparison on 2.1.223 (kaendar-lp)
started from <project-root>        ->  Registered 1 hooks from 3 plugins
started from <project-root>/src        ->  Registered 0 hooks from 1 plugins

# Minimal synthetic repo below, 2.1.228, grep -c HOOK_FIRED on the debug log
started from repro                 ->  1
started from repro/sub/deeper      ->  0

# There is no error message. That is the core of the report: nothing is printed.

Steps to Reproduce

  1. Create a minimal project with a hook that is impossible to miss:

mkdir -p repro/.claude repro/sub/deeper
cd repro && git init -q .
cat > .claude/settings.json <<'JSON'
{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command",
"command":"echo HOOK_FIRED >&2; exit 2"}]}]}}
JSON

  1. Start Claude Code from the project root and ask it to run any Bash command:

cd repro && claude

The hook fires and the command is blocked.

  1. Start Claude Code from the subdirectory and ask for the same command:

cd repro/sub/deeper && claude

The hook does not fire and the command runs. No warning is shown.

  1. Headless equivalent, if you want the count rather than the behaviour:

printf 'run: echo hi' | claude -p --debug hooks --debug-file /tmp/d.log
grep -c HOOK_FIRED /tmp/d.log

1 when started from repro, 0 when started from repro/sub/deeper.

  1. To confirm it is not variable expansion: replace the hook command with a

hard-coded absolute path to a script (no ${CLAUDE_PROJECT_DIR}, no variables).
It still does not fire from the subdirectory.

  1. To see the asymmetry: put a distinctive string in <project-root>/CLAUDE.md,

start from the subdirectory, and ask for it with "answer without reading any
files". It is answered from context, in the same session where the hook is dead.

Note for step 4: in a project with no plugins, the Registered N hooks from M plugins
line counts plugin hooks only and stays at 0 either way, so use HOOK_FIRED there.
In a project with plugins installed, that line is the clearer indicator.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.228 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Scope of the impact

  • It is total, not partial. All hooks declared in settings.json and all entries in

enabledPlugins are inactive.

  • It is silent. Nothing is printed at startup, and the session otherwise behaves normally.
  • /hooks cannot be used to detect it. It lists only settings.json-derived hooks, so it

prints "0 hooks configured" even in a healthy session whose hooks come from plugins.

  • Reproduced in two versions (2.1.223, 2.1.228) and in three repositories, on macOS.

Why this is hard to work around from the project side

  • A plugin hook cannot detect that it was never loaded.
  • Putting a check in CLAUDE.md does reach the model, since CLAUDE.md is loaded, but it

relies on the model choosing to run and act on it. In our testing, asking the session to
run which ainv-review produced an answer sourced from the installed-plugins registry
file rather than from PATH, which defeats the check.

  • Placing .claude/settings.json in every subdirectory is not viable: the set of

directories cannot be enumerated, and duplicated configuration drifts.

  • A shell wrapper that cd's to the git root before exec'ing claude does work, and is what

we currently use, but it is per-machine and per-shell, so a project cannot ship it.

Related issues

  • #10367 — same bug, all hook types, v2.0.27. Closed NOT_PLANNED by the autoclose bot.
  • #8810 — same bug, UserPromptSubmit hooks. Closed, also autoclose.
  • #6305 — Pre/PostToolUse hooks not executing. Still open.
  • #77464 — requests the opposite for the Agent SDK (settingSources: ["project"] should not

traverse parent directories). Worth reading together with this one, since a fix should
probably make the traversal rule explicit and consistent rather than implicit.

View original on GitHub ↗

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