Error: ENOENT: no such file or directory, scandir 'C:\ProgramData\ClaudeCode\.claude\skills'
Something asked Windows to list the contents of a directory that does not exist,
and the failure came back with the directory’s full path attached. This is a
local filesystem error: nothing reached the network, so it is not an auth
failure, not a quota or rate-limit problem, and not the API being down. It is
also not the other ENOENT people hit with Claude Code — spawn node ENOENT
means a program could not be located on PATH, which is a different fault
with a different fix.
Data as of 2026-09. Vendor paths and defaults change; check the official docs for current values before acting on anything below.
The word that carries the information is scandir
scandir is a directory enumeration, not a file open. Some component said
“list everything in this folder” and the folder was not there. Enumerating a
directory is almost always a probe: code checking whether something optional has
been installed, rather than loading a file it knows it needs. That is why this
error frequently appears in a terminal where everything otherwise works — a
probe that should have been a silent miss got reported as a hard failure.
So the useful question is not “what does ENOENT mean”. It is: which component went looking, and was it looking in the right place? The path answers the second half of that, and the answer is the reason this page exists.
C:\ProgramData\ClaudeCode\ is the old machine-wide root
Claude Code’s documented per-machine settings directory on Windows is
C:\Program Files\ClaudeCode\, holding managed-settings.json and, optionally,
a managed-settings.d\ directory and managed-mcp.json. The documentation is
explicit that the legacy Windows location,
C:\ProgramData\ClaudeCode\managed-settings.json, is not read.
The path in your error is rooted in that legacy location. That single fact disposes of the fix almost everyone tries first.
Creating C:\ProgramData\ClaudeCode\.claude\skills to make the message go
away is the cheap wrong fix. It will work, in the narrow sense that an
enumeration of an existing empty directory succeeds and the error stops
printing. What it will not do is make anything in that directory take effect,
because the machine-wide root under ProgramData is documented as not being
consulted. You trade a visible error for an invisible assumption, which is a
worse position than the one you started in — you now believe a deployment step
succeeded, and nothing will contradict you until someone asks why the policy
isn’t applied.
One boundary worth stating plainly rather than papering over: what the docs
verify is that the legacy managed-settings.json path is not read, and that the
machine-wide directory now lives under Program Files. A managed skills
directory is not documented at either root. So do not draw the mirror-image
conclusion either — moving a .claude\skills tree from ProgramData to
C:\Program Files\ClaudeCode\ is not a documented way to deliver skills
machine-wide, and there is no published contract saying it will be read there.
Check the current Claude Code documentation before building a deployment on it.
One more caution specific to this product: the documentation version-gates
individual behaviors with “Requires v2.1.NNN or later” and “Before v2.1.NNN”
notes, and a large fraction of the error reference carries them. Run
claude --version and compare against the current docs before you conclude that
your build behaves the way any write-up — including this one — describes.
Is retrying useful?
No. A missing directory is deterministic, and the second attempt enumerates the same absent path. There is nothing time-dependent here: no capacity to recover, no socket to reopen, no backoff that helps. Re-running the command produces the identical error at the identical moment.
There is a second signal in how the error reached you. Claude Code retries
transient failures internally — up to ten attempts with exponential backoff —
before showing anything, and while it does, it prints a Retrying in Ns · attempt x/y line. A filesystem error that appears instantly, with no retry
line before it, was never on the retry path to begin with. Treat that as
confirmation that you are looking at a deterministic local fault and go straight
to diagnosis.
Four checks that tell you whose path this is
Run these in order. Each one either indicts a component or clears it.
Is anything actually broken? Run claude doctor from the shell — it is the
read-only installation and settings diagnostic that works without starting a
session — and /doctor inside one. If both come back clean and the CLI does its
job, the ENOENT is a noisy probe of an optional location, not a broken install.
Worth knowing before you start rearranging directories: installing Claude Code
creates no settings file at all, so an absent directory under a config root is
the normal state, not damage.
Who printed the line? If the ENOENT arrived alongside
Error: Claude Code process exited with code 1, that wrapper line comes from VS
Code or an SDK application, not from Claude Code itself; the exit code alone
identifies nothing, and the real error lives in the process output. Decide
whether the ENOENT is the cause or merely the loudest line in the log before you
act on it.
Is the root your own configuration? Print CLAUDE_CONFIG_DIR in the
environment the failing process actually runs in — not your interactive shell,
if the failure happens under a service, a scheduled task, or CI. That variable
relocates settings, session history and plugins, and if it points at
C:\ProgramData\ClaudeCode, the path in the error is something your machine was
told to use, not a product default.
Is a customization doing the enumeration? Start with claude --safe-mode,
which runs with all plugins, MCP servers and hooks disabled. If the message
disappears, the caller is one of those, not the CLI core — and the directory it
wants is that component’s business, documented by that component.
Fixes, keyed to the check that selected them
--safe-modesilences it — re-enable plugins, MCP servers and hooks one at a time until the message returns, then take it up with whatever you just re-enabled. Do not create the directory to appease it; you would be hiding the one signal that identifies the culprit.CLAUDE_CONFIG_DIRpoints atProgramData— that is where the root came from. Point it at a directory the running account can actually read and write, or unset it, then start a new shell or restart the service so the change is inherited. Editing a variable in one window and testing in another is the usual reason this appears not to work.- You are deploying machine-wide settings — the documented Windows location
is
C:\Program Files\ClaudeCode\managed-settings.json, with an optionalmanaged-settings.d\andmanaged-mcp.jsonbeside it. If your rollout script or internal runbook writes toProgramData, it predates the change and is delivering nothing. - You only want your own skills and settings — your personal config root on
Windows is
%USERPROFILE%\.claude, which is what the docs write as~/.claude. A machine-wide directory is not where per-user configuration goes, and nothing you put underProgramDatasubstitutes for it. - Nothing is broken and the line is cosmetic — record
claude --versionand the exact string, then check the current error reference before filing. The wording of these messages changes often, sometimes within a minor version.
How to confirm it’s fixed
Open a fresh shell so any environment change is genuinely inherited, re-run the exact command that produced the error, and confirm the string is absent twice in a row. One clean run after an environment edit can just mean the old value was cached in the old process.
Then confirm the thing you actually cared about, because silence is not evidence:
- If you were delivering managed settings, run
/status, which lists the active settings sources and which credential is in use. The file you placed must appear in that list. If it does not, it is not being read, regardless of whether the ENOENT stopped. - If you were trying to load a skill, invoke it by name and watch it run. A quiet terminal proves only that nothing enumerated a missing folder.
claude doctorshould report clean both before and after; if it reported a problem before, that problem — not this message — was your actual fault.
Related errors
The other ENOENT in this cluster is a genuinely different failure:
Failed to spawn Claude Code process: spawn node ENOENT
is PATH resolution failing to find the node executable, so the fix lives in
how your shell or wrapper resolves programs, not in any directory you can
create.
If your missing path is inside a container or a WSL filesystem rather than on the Windows disk, check the mount before you check the directory — a failed share can make a whole tree look absent. Both Plan9 mount failed: invalid argument and failed to ensure virtiofs mount: Plan9 mount failed: bad address produce exactly that illusion, and no amount of creating directories inside the guest fixes a share that never mounted.
If you are not yet sure which layer you are on — local filesystem, mount, PATH,
or the API — the AI coding error triage tool
sorts these by the signals above so you spend your next ten minutes on the right
one.