asgeirtj/system_prompts_leaks · error · Error
${rel.split('/')[0]} not found under --node-modules (no ${jo
Error message
${rel.split('/')[0]} not found under --node-modules (no ${join(nodeModules, rel)}). In a hoisted monorepo the package's own node_modules is sparse — pass the repo-root node_modules instead. What it means
vendorReact() in emit.mjs reads react and react-dom files from the path passed via --node-modules using a readOrRemedy helper (line 29). In hoisted monorepos (yarn node-modules linker, npm workspaces), react — or just react-dom when it is only a peerDependency — lives in the repo-root node_modules while the synced package's own node_modules is sparse. The helper catches ENOENT and rethrows with the remedy message rather than walking up the directory tree: the comment at lines 25-28 explains the rest of the pipeline (esbuild nodePaths, token/css scrapes) runs against the same root, so silently healing only this one read would leave the build half-resolved.
Source
Thrown at Anthropic/Claude Code/bundled-skills/design-sync/lib/emit.mjs:34
} from 'node:fs';
import { join, resolve } from 'node:path';
import { escapeHtml, IIFE_IMPORT_META_DEFINE, readText } from './common.mjs';
import { previewExamples } from './docs.mjs';
// React ≤18 ships UMD; React 19 dropped it, so we bundle our own IIFE.
export async function vendorReact({ nodeModules, out }) {
// Hoisted monorepos (yarn node-modules linker, npm workspaces) keep react
// — or just react-dom, when it's only a peerDependency — in the REPO-ROOT
// node_modules; the synced package's own dir is sparse. Fail fast with the
// remedy rather than walking up: the rest of the pipeline (esbuild
// nodePaths, token/css scrapes) runs against the same root, so healing
// only this read would leave the build half-resolved.
const readOrRemedy = (rel) => {
try {
return readFileSync(join(nodeModules, rel), 'utf8');
} catch (e) {
if (e?.code !== 'ENOENT') throw e;
throw new Error(
`${rel.split('/')[0]} not found under --node-modules (no ${join(nodeModules, rel)}). ` +
'In a hoisted monorepo the package\'s own node_modules is sparse — pass the repo-root node_modules instead.',
);
}
};
const reactPkg = JSON.parse(readOrRemedy('react/package.json'));
// Both branches assign under a temp global then `||=`-merge so a host
// page's existing React isn't clobbered.
const noClobber =
';window.React=window.React||window.__dsReact;' +
'window.ReactDOM=window.ReactDOM||window.__dsReactDOM;' +
'try{delete window.__dsReact;delete window.__dsReactDOM;}catch(e){}';
const reactUmd = join(nodeModules, 'react/umd/react.development.js');
if (existsSync(reactUmd)) {
writeFileSync(
join(out, '_vendor', 'react.js'),
';(function(){var __r=window.React,__rd=window.ReactDOM;' +
readFileSync(reactUmd, 'utf8') + '\n' +View on GitHub (pinned to 93c999115b)
Solutions
- Pass the repo-root node_modules to --node-modules (e.g. ./node_modules, not ./packages/my-ds/node_modules).
- If react is not at the repo root either, install it there: run `npm install react react-dom` (or the workspace-equivalent install) at the monorepo root.
- Verify the path resolves: confirm <node-modules>/react/package.json and <node-modules>/react-dom/umd/react-dom.development.js (for React ≤18) exist at the path you pass.
Example fix
# before — points at the sparse package-local node_modules design-sync ... --node-modules ./packages/design-system/node_modules # after — points at the hoisted repo-root node_modules design-sync ... --node-modules ./node_modules
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'node:fs';
import { join } from 'node:path';
// Validate the --node-modules path points at a dir that actually
// contains react BEFORE invoking vendorReact / design-sync.
function validateNodeModules(nodeModules) {
const reactPkg = join(nodeModules, 'react', 'package.json');
if (!existsSync(reactPkg)) {
throw new Error(
`react not found under ${nodeModules}. ` +
'In a hoisted monorepo, pass the repo-root node_modules.'
);
}
return true;
} Try / catch
// Intentional fail-fast — do NOT catch and walk up the tree. // The comment in emit.mjs explains: the rest of the pipeline uses // the same root, so healing only this read leaves the build half- // resolved. Fix the --node-modules argument instead.
Prevention
- In a hoisted monorepo, always pass the repo-root node_modules to --node-modules, not a workspace package's own directory.
- Before running design-sync, verify with `ls node_modules/react/package.json` at the path you intend to pass.
- If react is only a peerDependency of your package, install it at the monorepo root (`npm install -w root react react-dom`) so it lands in the hoisted node_modules.
- Script the design-sync invocation from the repo root so the relative --node-modules path always resolves to the hoisted store.
When it happens
Trigger: Passing --node-modules <package-dir>/node_modules in a hoisted monorepo where react was hoisted to the workspace root. The first readOrRemedy call (react/package.json at line 40, or react-dom/umd/react-dom.development.js at line 53) hits ENOENT and throws the remedy error.
Common situations: Yarn workspaces (node-modules linker) or npm workspaces that hoist react to the repo root. Pointing --node-modules at a synced/vendored package directory instead of the repo root. A package that lists react-dom only as a peerDependency, so it is never installed in the package's own node_modules. Running design-sync against a single workspace package path rather than the monorepo root.
Related errors
AI-assisted analysis of asgeirtj/system_prompts_leaks@93c999115b (2026-08-13).
Data as JSON: /api/errors/abce95707ca993dc.
Report an issue: GitHub.