Claude Code ignores the CLAUDE_CODE_PLUGIN_DIRS key when it is set in a project's .claude/settings.json. Project-scoped settings cannot set it, and the only place that is stated is one line in the --debug-file log. Two of our guardrail mods sat behind that key and loaded in no session for three days.
The line reads, in substance, that CLAUDE_CODE_PLUGIN_DIRS in .claude/settings.json is ignored because project-scoped settings can't set this key. Nothing appears in the session itself. The mods simply are not there, and a missing hook looks the same as a hook that has nothing to say.
The two mods, a founder tap gate for merges and deploys and a merge-readiness check, merged on October 8. Between then and October 11 neither ran anywhere. Setting the variable in the process environment made both load, in the terminal CLI and in the VS Code extension's 2.1.295 binary.
When the mods did not show up in an extension session, the repo's CLAUDE.md, both mod READMEs and the status board all concluded that the extension's bundled CLI could not load mods. It was a plausible story, it matched the symptom, and nobody checked it against the log. Three documents repeated it until it read as established.
The first test of the gate was its deny path, not its version string.
That is how it surfaced. A pull request in the identity app merged without a tap card, which a loaded gate would have held. Asking why led to the debug log, and the log answered in one line.
The key was removed from the repo's settings file and now lives in the user-scope settings, where Claude Code honours it. The repo docs and the status board were corrected to say what the log says, and the lesson went into memory as a rule: this variable must be user scope.
The wider habit is the one to keep. A guardrail needs a check that runs in a real session and watches it refuse something. A passing unit suite proves the mod's code works; it says nothing about whether any session loads it. Until the mods had a fresh session confirming both load, the older Python hooks they mirror stayed wired in parallel, which is why the gap was three days of weaker guardrails rather than none.
This post was drafted by an AI system from Dekimu's public engineering record and published with automated checks, without per-post human editing.
← Back to blog