"It worked yesterday and today the tool edits land in the wrong folder." That kind of report usually sounds like a regression, and my instinct for a long time was to grep through recent commits first. The reality in most of my own cases turned out to be simpler: the environment drifted, not the code. And in most of those cases, five seconds with /doctor would have pointed at the drift right away.
This article walks through the output of /doctor section by section, through the lens of the specific mistakes it has saved me from. The command itself shows up in the official docs and the /help listing, but the question of which parts of the output actually deserve your attention is rarely discussed. The sections below walk through the patterns I now treat as early warning signs — hopefully they'll save you the same hour they've saved me on more than one occasion.
Four kinds of information /doctor gives you
/doctor is an internal slash command you run from inside an interactive Claude Code session. Its output clusters into four signals worth reading carefully.
- Runtime: Node version, OS, the installed
claude-codeCLI version, and which account is authenticated - Settings merge: how
~/.claude/settings.json, the project's.claude/settings.json, and.claude/settings.local.jsoncombined — including which file won each key - MCP servers: registered server names, their launch commands, and the connection state (connected, failed, or timing out)
- Permission mode: Plan, Accept Edits, or Default; plus the active
allowedToolslist
If you can tell which of these four signals carries the unexpected value, you have narrowed down the root cause to a single layer. That alone is usually 80% of the work.
Scenario 1: A home-directory setting silently overriding the project
A colleague reported that npm install kept running at session start, even though his project was configured for pnpm. The culprit turned out to be a stale ~/.claude/settings.json entry that set env.NPM_CLIENT=npm globally, from an unrelated project he'd touched a week earlier.
# Launch a session and run /doctor to see how env resolved
claude
# Inside the prompt
/doctorThe settings section of /doctor prints not just the effective values, but also the source file each value came from. An excerpt for this case looks like the following.
Settings merge:
~/.claude/settings.json env.NPM_CLIENT = "npm" (source)
./.claude/settings.json (no entry)
./.claude/settings.local.json (no entry)
→ effective env.NPM_CLIENT = "npm"
Once you can see that the user-level file owns the key, the fix is straightforward: declare the key again in the project-level settings.json so it wins the merge. A common follow-up mistake is to delete the user-level entry instead; that works for the project you're in right now, but it breaks the next project that was quietly relying on the inherited value. Re-declaring at the project level is almost always the safer move, and it also makes the configuration self-documenting for anyone who clones the repo later. I've covered the full merge rules in the Claude Code settings.json guide for anyone who wants the complete reference.
Scenario 2: An MCP server that fails quietly
MCP servers are the failure mode I underestimated the longest. A broken server does not stop Claude Code from launching — it just never shows up in your available tools. I've personally shipped a Python-based MCP server that failed to resolve its virtualenv path on one of my machines, and it took me longer than I care to admit to notice it wasn't running.
The MCP section of /doctor reports, per server, the connection state, the time to the first handshake, and the last one or two lines of standard error. A typical output looks like this.
MCP servers:
filesystem ● connected (0.12s)
github ● connected (0.31s)
my-python-agent ✕ failed ModuleNotFoundError: No module named 'agent_core'
notion ⚠ timeout (>10s; still trying)
The distinction between failed and timeout matters. A failed server almost always points at the launch command itself — run claude mcp test my-python-agent to reproduce the error outside of the session. A timeout is more often a missing environment variable or a blocked network call, and running the process by hand with full stderr attached usually reveals the cause in a few seconds. The subtler case is a server that reports connected but whose tools never actually show up in Claude's tool list; when that happens, I tend to check whether the server is advertising its tools properly by printing the handshake response from a bare client script before assuming Claude Code itself is to blame.
If the server is connected but your hooks still don't fire, switch focus and walk through the Claude Code hooks not firing checklist — the patterns there tend to dominate at that stage.
Scenario 3: A stale CLI version hiding a fixed bug
The runtime line of /doctor is easy to skim past, but I've paid for that habit more than once. A Node version that is two majors behind what the CLI expects, or an claude-code binary installed globally a few months ago and forgotten, can produce symptoms that look exactly like a settings problem. The fix is trivial — update the CLI — but only if you notice the mismatch in the first place.
Runtime:
OS macOS 15.3 (arm64)
Node v18.20.2 (npm 10.5.0)
claude-code v2.0.12 (released 2026-01-09) ⚠ 4 minor versions behind
authenticated you@example.com (workspace: personal)
When the CLI line includes a "behind" note, I treat it as a red flag. Most breaking changes in this project have landed through subtle shifts in the default permission mode or MCP bootstrap order — both of which disproportionately break automation that was set up months earlier. A one-line npm install -g @anthropic-ai/claude-code@latest (or your package manager's equivalent) before continuing the investigation has saved me from chasing ghosts in more than one session. The matching habit I've built is to pin the CLI version in a project-local tooling manifest whenever the team is larger than me, so that everyone sees the same behavior regardless of when they last ran the global install.
Scenario 4: Mistakenly running in Accept Edits mode
"Claude keeps making edits I didn't approve" has, more than once in my own history, turned out to be a session launched in Accept Edits mode without the developer realizing it. The shortcut or shell alias they were using had --permission-mode acceptEdits embedded in it from an earlier experiment.
Permission mode: acceptEdits
allowedTools: Read, Edit, Write, Bash(git:*)
denyTools: -
source: command-line flag (--permission-mode acceptEdits)
The source line is what makes this easy to catch. When it reads command-line flag rather than settings.json, you know to inspect the launch script, the editor integration (for example the VS Code extension's settings panel), and shell aliases — in that order. If the source is a settings file you didn't expect, the merge section from Scenario 1 becomes the next thing to read carefully; the same file that won the permission-mode key is likely winning several other keys you haven't audited yet.
Handing the /doctor output directly to Claude
If reading the output still leaves you unsure, the highest-leverage move is to paste it into the conversation and ask Claude what it means. Because you are inside Claude Code, the agent can correlate the output against the project and suggest the next concrete step — occasionally including links to runbooks. One class of symptoms that responds well to this approach is the Windows-specific PowerShell tool switch; if any of the patterns described in the Windows PowerShell tool troubleshooting notes ring a bell, start there.
Because /doctor does not log its own output anywhere by default, capturing a snapshot on the day you change your environment saves you real time later.
# Keep a dated snapshot of the session (macOS / Linux)
claude 2>&1 | tee ~/.claude/logs/session-$(date +%Y%m%d-%H%M).logThe one-liner above is noisy if left on for a full session, so I usually start it, run /doctor, and quit. What matters is having one clean snapshot of the environment per change-day — differences between snapshots compress troubleshooting time dramatically.
Making it a reflex
Looking back across the cases I've described here, I am comfortable saying /doctor should be the first command you run when something in Claude Code feels off. It lets you audit the environment in under a minute, and even when it does not give you the root cause, it hands you a much smaller search space.
In practice, a three-step reflex covers the vast majority of incidents. What matters most is resisting the temptation to start grepping through recent commits before you've looked at the environment — that is the single most time-expensive wrong-turn in my own history with this tool.
- Open a session and scan all four groups in the
/doctoroutput - If any MCP server is
failedortimeout, reproduce the launch by hand - If the merged settings don't match your mental model, re-declare the key in the project's
settings.json
A concrete first step you can take today is to launch Claude Code the way you normally do — through your aliases, scripts, or IDE integration — and read the /doctor output with fresh eyes. You will probably find a value coming from a layer you had forgotten about. To go deeper on the settings layer specifically, the settings.json guide is a good entry point, and for bash-tool edge cases I've collected the patterns in the Claude Code bash execution errors guide.