[BUG] ConfigChange hook emitting blocking decision is not respected by claude code CLI
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)
- [ ] I am using the latest version of Claude Code
What's Wrong?
I've created a ConfigChange hook that checks if the settings.json files are valid JSON (common pitfall: editing the settings mid-session) and the hook is supposed to block if it isn't.
Through the hook script's logging utility I can see that it does fire and does report broken JSON. Testing the hook from the command line shows the same behavior.
Both emitting a JSON decision to STDOUT, and exiting with code 2 fails to interrupt the session in the face of a broken settings.json
The hook is part of a suite of hooks that are also used on PreToolUse and elsewhere, using the same library code, and all of those work correctly. This seems to be a clear problem in how the CLI handles the return from the ConfigChange hook.
What Should Happen?
The session should immediately interrupt upon a ConfigChange hook outputting a blocking JSON decision or exiting with code 2.
Error Messages/Logs
Steps to Reproduce
- Add a ConfigChange hook to the project settings
.claude/settings.jsonwith the command"exit 2"to output a blocking exit code - Run a claude code session (use a dummy first prompt like 'Hi claude')
- Edit the
.claude/settings.jsonin some way
and
- Add a ConfigChange hook to the project settings
.claude/settings.jsonwith the command"echo '{\"decision\":\"block\",\"reason\":\"none\"}'"to output a blocking decision to STDOUT - Run a claude code session (use a dummy first prompt like 'Hi claude')
- Edit the
.claude/settings.jsonin some way
Claude Model
None
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.117
Claude Code Version
2.1.196
Platform
Other
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
This is a pretty critical security flaw.
3 Comments
Could not reproduce on v2.1.233 (macOS) — the ConfigChange block is respected in both forms (exit code 2 and a JSON
decision: "block").Steps:
.claude/settings.jsonwhose command isexit 2(also triedecho '{"decision":"block","reason":"none"}').claude --debug-file ./debug.txtand send a first prompt ("Hi claude")..claude/settings.jsonto add a UserPromptSubmit hook that rejects every prompt (echo NEW_HOOK_APPLIED >&2; exit 2).Observed: the second prompt goes through normally — the newly added hook was not applied, i.e. the change was blocked.
debug.txtshowsConfigChange:project_settings [exit 2] completed with status 2followed byConfigChange hook blocked change to .../.claude/settings.json. Control: with no ConfigChange hook, the same edit takes effect immediately and the next prompt is rejected withNEW_HOOK_APPLIED.Expected: the same. The session is not interrupted, and no message is shown.
Assessment: this is working as designed — see https://code.claude.com/docs/en/hooks#configchange-decision-control. The intended model is that a blocking ConfigChange hook prevents the new settings from being applied to the running session; it does not stop or interrupt the session, and by design it surfaces no message to you or to Claude (only a debug-log line). This has been the behavior since ConfigChange hooks were introduced, including v2.1.117. Note that in your specific case (blocking on invalid JSON) a block is invisible for another reason: an unparseable settings file can't be applied either way, so blocked and not-blocked look identical. If you want the session to stop, that would be a feature request rather than a bug. If you are actually seeing new, valid settings take effect despite the block, please share which file you edit (
policy_settingschanges cannot be blocked) and a--debug-filelog.🤖 Generated with Claude Code
Thanks for the detailed report and repro script. I tested this on 2.1.233 (Linux): fresh config dir, a
ConfigChangehook on~/.claude/settings.json, then edited the file externally while a session was running.What I observed: the hook fires, and both
{"decision":"block"}andexit 2are respected — the edited settings are not applied to the running session (visible with--debug: "ConfigChange hook blocked change to …"; a control run without the block shows the settings reload happening). So the blocking decision is working.The part that differs from your expectation is what a block does: per the hooks docs, a ConfigChange block "blocks the configuration change from taking effect" — it prevents the new settings from being applied, but it does not interrupt the session, and no message is shown in the UI (only a debug-log line). That silence is intended behavior today, so I'm closing this as working-as-documented — reply if you can show the blocked settings actually taking effect in your session and we'll reopen.
I agree the zero-feedback block is easy to read as "not respected"; visible feedback on blocked config changes would be a reasonable feature request.
🤖 Generated with Claude Code
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.