AniUI Academy

Debugging Your Configuration

A diagnostic order for configuration that does not work — what actually loaded, which copy of it won, whether it ever fired, and how to bisect the rest.

11 min read

Everything in this part of the course has a failure mode in common. You write the file, you start a session, and nothing changes. The instinct at that point is to conclude that Claude is ignoring you, and to write the instruction again, louder.

Almost always something more boring is true. The file did not load. It loaded from a different place than you thought. Another file overrode it. The hook is registered but its matcher never matches. Each of those has a different fix and all of them look identical from the outside.

So the skill worth having is not a list of symptoms. It is an order of questions.

The order

  1. Step 1

    Did it load?

    Look at what is actually in the session rather than what you believe you configured. If it is absent, this is a location or syntax problem and nothing else matters.

  2. Step 2

    Is it binding?

    If it loaded and is being disregarded, check what kind of thing it is. Advisory mechanisms are allowed to be disregarded, which makes this a wording problem, not a wiring one.

  3. Step 3

    Which copy won?

    The same key can exist in four scopes. When a value seems inert, something above it in the precedence order is usually setting it too.

  4. Step 4

    Did it fire?

    Registered is not the same as triggered. For anything event-driven, confirm the event happened and the matcher matched before doubting the command.

Work down. Most problems resolve at the first question.

The commands that answer them

/context is the first move for almost everything. It reports what is occupying the window right now, broken down: the system prompt, tool definitions, MCP tools, custom subagents and where each was loaded from, memory files, skills, and the conversation itself. If your CLAUDE.md is not in that list, Claude cannot see it, and no amount of rewording will help.

From there the specific commands are narrower and faster.

CommandWhat it tells you
/contextEverything in the window, and how much space each part takes
/memoryWhere your memory files are, across user and project scope
/skillsWhich skills were found, and from which source
/hooksEvery registered hook, grouped by event
/mcpEach configured server, its connection status and its tool count
/permissionsThe allow and deny rules actually in effect
/statusWhich settings sources loaded, including managed ones
/doctorA setup checkup: invalid settings files, duplicate definitions, and proposed fixes

/doctor is the one to reach for when you do not yet have a hypothesis, because it looks for the mistakes you would not think to check — a settings file that failed to parse, two subagents in one directory claiming the same name. It proposes fixes and applies them only after you confirm. From your shell, claude doctor prints the read-only version without starting a session, which is what you want when Claude Code will not start.

One more, for a genuinely opaque problem: /debug turns on debug logging for the session and asks Claude to diagnose using the log and your settings paths.

Precedence, which explains most silent failures

Settings merge across four scopes, and when the same key appears twice the narrower one wins. Managed settings deployed by your organisation sit at the top and cannot be overridden. Below that come command-line flags for the session, then the gitignored local file, then the project file, then your personal user file.

That order produces one specific, extremely common confusion. Your .claude/settings.local.json is not committed, and you almost never open it, so a value you set there once will quietly beat the project setting you are currently editing. /status tells you which sources are loaded at all; the local file is where to look next.

Two more traps in this area are worth memorising, because neither produces an error message.

~/.claude.json and ~/.claude/settings.json are different files. The first holds application state — your login, per-project trust decisions, your personal MCP servers. Permissions, hooks and environment variables belong in the second. Putting them in the first is silently ignored.

And a user, project or local settings file that fails validation is rejected as a whole, not partially. One trailing comma and every hook in that file disappears from /hooks. Managed settings behave differently on purpose — an invalid entry there is stripped and the rest of the policy still applies — which is why a typo cannot disarm an organisation's policy.

Not everything follows the override rule, either. Permission rules merge across scopes rather than replacing one another, and CLAUDE.md files accumulate rather than override, which is why two contradictory rules produce inconsistent behaviour instead of a clear winner.

Walking it once

claude
  1. WhyThe setup: a hook that should run the type checker after every file edit. It was working last week. Today it is not, and the temptation is to start editing the script.

  2. You typed: /hooks

  3. Output: No hooks configured.
  4. WhyQuestion one, answered in four seconds. This is not a matcher problem or a script problem — nothing is registered at all. Every hypothesis about the command was wasted breath.

  5. You typed: /doctor

  6. Output: Installation   OK
    Settings
      .claude/settings.json   invalid JSON at line 14
      ~/.claude/settings.json OK
  7. WhyThere it is. A settings file that fails to parse is rejected entirely, so the hook did not half-load — it was never read. Note that the file was still edited successfully and committed; nothing warned anybody.

  8. You typed: Fix line 14 of .claude/settings.json.

  9. Claude ran: Read .claude/settings.json

  10. Claude ran: Edit .claude/settings.json

  11. Claude replied: A trailing comma after the last hook entry. Removed. Settings files reload without a restart, so /hooks should show it now.

  12. You typed: /hooks

  13. Output: PostToolUse (1)
      matcher   Edit|Write
      type      command
      source    .claude/settings.json
  14. WhyRegistered is still not the same as working. One more step: cause the event and look for the effect, rather than assuming.

  15. You typed: add a nullable note field to the Invoice type

  16. Claude ran: Edit src/types/invoice.ts

  17. Output: tsc --noEmit: 0 errors
  18. WhyFour commands, no guessing, and at no point did anyone read the hook script. The order did the work: is it there, why not, is it there now, does it fire.

Two of the four questions were enough, which is typical.

The three that come up most

Each of these is the general procedure applied to one mechanism.

A hook that does not fire. Run /hooks. If it is absent, the settings file is the problem: hooks live under a hooks key in a settings file, and there is no standalone hooks file for project or user configuration — only plugins use a separate hooks/hooks.json. If it is present, suspect the trigger. Matchers are case-sensitive and match tool names exactly, so bash matches nothing. Use | to list several tools rather than an array, which is a schema error and takes the whole file down with it. And remember which tool actually did the thing: a matcher on Edit never sees a file rewritten by a shell command. If it is registered and the matcher looks right, start the session with claude --debug and trigger it while watching the log, which records what matched and how it exited.

A skill that is never picked up. Run /skills. If it is missing, check the layout: a skill is a directory with SKILL.md inside it, not a single markdown file named after the skill. If it is listed but Claude never reaches for it, either it is marked as yours to invoke only, or the description does not use the vocabulary of your actual request. Say the request out loud, then look at whether those words appear in the description.

An MCP server that will not connect. Run /mcp. A project-scoped server from .mcp.json needs a one-time approval, and if you dismissed that prompt the server stays disabled until you approve it there. A server that fails to start is usually a relative path in its command or arguments, which resolves against the directory you launched from rather than the location of the config file — use absolute paths. A server that shows as connected with zero tools has started but is not returning a tool list; reconnect it from /mcp. And check the location before anything else: .mcp.json belongs at the repository root, not inside .claude/, and a settings file does not read an mcpServers key at all.

When the window is the problem

/context earns its place twice. Beyond confirming what loaded, it shows you what is taking up room, which diagnoses a different class of complaint — Claude losing track of your conventions, or skills that stop triggering reliably.

Look for a CLAUDE.md that has grown past a couple of hundred lines, a large skill body that was pulled in early and has stayed since, or several MCP servers whose tool listings you are paying for in every request. A configuration problem and a crowding problem feel the same from the outside, and only one of them is fixed by editing files.

The same instinct applies to anything else you might be assuming about your session, permission mode included. Do not reason from what the default ought to be — the status bar tells you which mode you are in.

When you cannot tell

If the four questions have not resolved it, stop narrowing and start bisecting.

claude --safe-mode starts a session with your customisations disabled — CLAUDE.md, skills, plugins, hooks, MCP servers, custom commands and agents. Authentication, models, built-in tools and permissions still work. If the problem vanishes, it lives in something you added, and the targeted checks above will find which. If it survives, you have ruled out all of it in one step.

For the harder case where your settings themselves are suspect, launch from a directory with no project configuration and point CLAUDE_CONFIG_DIR at an empty directory, which bypasses everything under ~/.claude. You will see the first-run setup screens, which is how you know it worked. Then reintroduce your files one at a time.

What to take away

Work down four questions in order: did it load, is it the kind of thing that is binding, which copy of it won, and did it actually fire. /context answers the first for almost everything, and answering it first is what stops you rewriting an instruction that never arrived. Settings merge with managed policy above command-line flags, then the local file, then the project file, then your user file, while permission rules merge instead of overriding and CLAUDE.md files accumulate — and a user or project settings file with one syntax error is rejected in full, silently. The specific commands narrow quickly: /hooks for registration, /skills for discovery, /mcp for connection and approval, /status for which sources loaded, /doctor when you have no hypothesis at all. When nothing has resolved, claude --safe-mode splits the problem cleanly in two, and a session pointed at an empty configuration directory rules out your own files entirely.

Next: Part 4, and the shift from sessions you sit in to Claude running without you — starting with claude -p, which turns the agent into something you can put in a script.

Check yourself

5 questions · pass 4/5 to unlock Non-Interactive Mode

up to 50
  1. 1.A rule you added to CLAUDE.md is being ignored. What is the first thing to establish?

  2. 2.A key in your project's .claude/settings.json appears to have no effect, and nothing else obviously conflicts. What is worth checking?

  3. 3.Your hook appears in /hooks under the right event but never runs. Which explanation fits that evidence?

  4. 4.Why is claude --safe-mode a useful step rather than a last resort?

  5. 5.You added an mcpServers block to .claude/settings.json and the server never appears. What is going on?

5 left to answer