Fixing Nx 'Failed to Process Project Graph'
Every Nx command starts by building the project graph: discovering projects, running plugins that infer targets, and analysing dependencies between projects. When any part of that fails, Nx stops before running a single task and prints Failed to process project graph. The message is generic because the causes are varied — a plugin crash, a malformed configuration file, a stale daemon, a version mismatch between Nx packages, or a file the source analyser cannot parse. This guide shows how to get the real error out of Nx, and the fix for each common cause.
Exact symptoms and error messages
The top-level message, often followed by a stack trace from a plugin:
NX Failed to process project graph.
An error occurred while processing files for the @nx/vite/plugin plugin.
- apps/web/vite.config.ts: Error: Cannot find module '@vitejs/plugin-react'
Require stack:
- /repo/apps/web/vite.config.ts
Variants name the daemon or a specific phase:
NX Daemon process terminated and closed the connection
Please rerun the command, which will restart the daemon.
If you get this error again, check for any errors in the daemon process logs found in: /repo/.nx/workspace-data/d/daemon.log
NX Failed to process project graph. Run "nx reset" to fix this. Please report the issue if you keep seeing it.
NX The following projects are defined in multiple locations:
- @acme/ui:
- packages/ui
- packages/ui-legacy
Root cause analysis
Project graph construction runs in stages, and each stage can fail independently. Knowing the stages tells you where to look. The overall architecture is covered in Nx Workspace Architecture.
The common causes, roughly in order of frequency:
- A plugin fails while loading a config file. Inference plugins such as
@nx/vite/pluginor@nx/jest/pluginactually loadvite.config.tsorjest.config.tsto read their settings. If that file imports a package that is not installed, uses an environment variable that is unset, or throws at load time, the plugin fails and so does the graph. - Stale cached state. The daemon or
.nx/workspace-dataholds a graph from before a branch switch, a dependency upgrade or a moved project. Nx usually detects this, but not always. - Mismatched Nx package versions.
nxat one version and@nx/viteor@nx/jsat another — often after upgrading only some packages — causes plugin API mismatches. - Duplicate or malformed project definitions. Two
package.jsonfiles with the samename, invalid JSON in aproject.json, or a project folder matched twice. - Source files the analyser cannot handle — rarely, a syntax Nx's parser does not support in a file it scans for imports.
Getting the real error
The first line is rarely enough, and guessing at the cause wastes time because several unrelated problems share the same headline. Turn off the daemon and turn on verbose logging to see the underlying exception:
NX_DAEMON=false NX_VERBOSE_LOGGING=true pnpm nx show projects
Running without the daemon puts graph construction in your terminal's process, so stack traces print directly instead of going to .nx/workspace-data/d/daemon.log. nx show projects is a cheap command that only needs the graph, which makes it a good reproduction.
Resolution
Fix the failing config file
If the error names apps/web/vite.config.ts, load that file directly to reproduce outside Nx:
cd apps/web && pnpm exec vite --version && node --import tsx -e "import('./vite.config.ts').then(() => console.log('config loads'))"
Typical fixes: install the missing plugin in that project's devDependencies (under pnpm's strict layout, a plugin installed only in another package is not resolvable here), guard environment variable access with defaults, or move side effects out of the config file. Config files should be cheap and side-effect-free, because Nx loads them for every graph computation.
Reset cached state
pnpm nx reset
This stops the daemon and deletes the local cache and workspace data. It is safe — the next command recomputes everything — and it fixes stale-state failures after branch switches, git clean operations or dependency upgrades.
Align Nx versions
pnpm list nx @nx/js @nx/vite @nx/eslint --depth 0
pnpm nx migrate latest # writes package.json updates and migrations.json
pnpm install
pnpm nx migrate --run-migrations
nx migrate updates every @nx/* package together and runs code migrations for configuration changes between versions. Never upgrade nx and its plugins independently.
Resolve duplicate projects
Two projects with the same name cannot coexist. Rename one package, or exclude the legacy folder from the workspace globs and from Nx discovery with .nxignore:
# .nxignore
packages/ui-legacy
examples/**
Isolate a misbehaving plugin
If the error is unclear, temporarily remove plugins from the plugins array in nx.json one at a time and rerun nx show projects. The plugin whose removal fixes the graph is the one to investigate; its include/exclude options can scope it away from folders it should not process.
Performance problems that look like failures
Some graph "failures" are really timeouts. Inference plugins load every matching config file on each graph computation, and in large repositories that can take long enough for the daemon connection or a CI step timeout to give up. Signals are messages about the daemon closing the connection with no underlying exception in daemon.log, or graph computation that takes tens of seconds on a cold start.
Three measures help. Scope plugins with include and exclude in nx.json, so @nx/vite/plugin only processes folders that are actually Vite projects rather than every file matching **/vite.config.*, including fixtures and examples. Keep config files light: a vite.config.ts that imports a large framework or reads the file system at the top level is slow to load for every graph computation. And make sure the plugin result cache works — Nx caches plugin output keyed by the config file contents, so a config file that embeds a timestamp or generated value defeats it and forces a reload every time.
To measure, run NX_PERF_LOGGING=true pnpm nx show projects and look at the time spent per plugin. A plugin that dominates the total is the one to scope or simplify.
After upgrading Nx
Many graph failures appear the first time a command runs after an Nx upgrade, because configuration formats move between versions. nx migrate --run-migrations applies the code changes that the Nx team ships for each release — renamed options, moved plugins, new defaults — and skipping it leaves the workspace half-upgraded. If a failure appears after an upgrade that was done by editing package.json directly, revert to the previous versions, then upgrade again through nx migrate, commit migrations.json together with the result, and delete it once applied.
Worked example: a CI-only graph failure
A pipeline fails at the first nx affected call with Failed to process project graph, while every developer's machine works. With NX_DAEMON=false NX_VERBOSE_LOGGING=true, the CI log shows the @nx/vite/plugin failing on apps/docs/vite.config.ts: Error: Missing required environment variable DOCS_API_KEY. The config file threw at load time when the variable was unset — developers had it in their shell. The fix moves the check into a Vite plugin hook that runs only during build, so loading the config for inference no longer needs the secret. The team adds a CI step that runs nx show projects with an empty environment to catch similar mistakes early.
Prevention and CI/CD guardrails
- Keep tool config files side-effect-free; read environment variables lazily inside hooks.
- Upgrade Nx only with
nx migrate, and keep all@nx/*packages on one version. - Run
nx show projectsearly in CI as a cheap graph health check with clear logs. - Use
.nxignorefor folders that contain package manifests but are not projects, such as examples and fixtures.
Frequently Asked Questions
Is it safe to run nx reset regularly? Yes. It only deletes caches and stops the daemon. The cost is that the next run recomputes the graph and rebuilds anything not in a remote cache.
Why does the error only happen with the daemon on?
The daemon can hold state from an earlier run, and its errors go to a log file rather than your terminal. Running with NX_DAEMON=false usually shows the real error, and nx reset clears stale daemon state.
Can a single broken project stop Nx for everyone? Yes, because the graph is computed for the whole workspace. That is why config files and plugin scopes deserve care in shared repositories.
Where are the daemon logs?
In .nx/workspace-data/d/daemon.log inside the repository. Read it when the terminal only shows that the daemon closed the connection; the underlying exception is usually there.
Related
- Nx Workspace Architecture explains the project graph Nx builds.
- Adding Nx to an Existing pnpm Workspace introduces the plugins and configuration involved.
- Fixing Nx 'Affected' Detecting All Projects as Changed handles a different graph-related CI problem.
- Debugging Circular Dependencies in Monorepos covers graph problems that do not stop Nx but distort it.