Both this and one other thread never got a follow-up so I decided to investigate this since we are running into the same issue here.
Solution: Remove node_modules before scanning in pnpm monorepos
We’ve been experiencing the same issue after migrating our large Nx monorepo (~200+ packages) from Yarn to pnpm. Here’s what we found and how we solved it.
The Problem
After switching to pnpm, our SonarCloud CI jobs started showing:
- Symlink loop warnings during analysis
- Significantly slower scan times
- Occasional OOM kills in CI
The root cause is that pnpm’s node_modules structure uses symlinks extensively. When you have a package at packages/my-app/ with sonar.sources=., the scanner follows symlinks inside packages/my-app/node_modules/.pnpm/ which contains thousands of nested symlinked packages.
Even with sonar.exclusions=**/node_modules/** configured, the scanner appears to traverse these directories before applying exclusions, causing the symlink loop warnings.
Our Solution
Since SonarQube/SonarCloud only needs:
- Source code (already in git)
- Coverage reports (generated by test jobs)
It does not need node_modules at all. We simply delete each package’s node_modules directory immediately before running the scanner.
For Nx monorepos, we do this inside a custom executor so it happens after Nx builds its project graph but before sonar runs:
// packages/nx-helpers/src/executors/sonarqube/executor.ts
import { existsSync, rmSync } from 'fs';
import { join } from 'path';
export default async function runExecutor(options, context) {
const projectRoot = /* ... get from context ... */;
// Remove node_modules to prevent sonar from traversing pnpm symlinks
// This is safe because sonar only needs source code and coverage reports
const nodeModulesPath = join(context.root, projectRoot, 'node_modules');
if (existsSync(nodeModulesPath)) {
console.log(`Removing ${nodeModulesPath} to speed up sonar analysis...`);
rmSync(nodeModulesPath, { recursive: true, force: true });
}
// Then run sonar-scanner
const command = `pnpm sonar -Dsonar.token=... -Dsonar.organization=... ...`;
// ...
}
Why not just use exclusions?
We tried several exclusion approaches:
-
sonar.exclusions=**/node_modules/** - Still causes symlink traversal warnings, suggesting the scanner walks the filesystem before applying exclusions.
-
More specific sonar.sources - Not practical in a large monorepo where each package has different source directories.
Results
- No more symlink loop warnings
- Faster scan times (less filesystem to traverse)
- No OOM issues
- Root
node_modules preserved (needed for nx and @sonar/scan CLI)
Environment
- pnpm 10.x
- sonar/scan 4.3.2
- SonarCloud
- Nx 22.x monorepo
Suggestion for SonarSource
It would be helpful if the scanner could:
- Skip symlink traversal entirely for excluded paths (don’t even
readdir them)
- Have a
sonar.skipSymlinks=true option to avoid following symlinks
- Better document pnpm-specific configuration for monorepos
Hope this helps others hitting the same issue!