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

  1. Pass the repo-root node_modules to --node-modules (e.g. ./node_modules, not ./packages/my-ds/node_modules).
  2. 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.
  3. 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

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.