mui/material-ui · error · Error

Duplicated icons in legacy folder. Either \n1. Remove these

Error message

Duplicated icons in legacy folder. Either \n1. Remove these from the /legacy folder\n2. Add them to the blacklist to keep the legacy version\nThe following icons are duplicated: \n${duplicatedIconsLegacy.join('\n')}

What it means

After generating icon files into outputDir, the builder globs the legacy/ folder and the generated files, computes their intersection by basename, and throws if any name appears in both. Legacy icons are meant to be one-of-a-kind overrides; a collision means a newly generated icon now shadows (or is shadowed by) a legacy one, so the build aborts and lists the duplicates.

Source

Thrown at packages/mui-icons-material/builder.mjs:282

        svgPath,
        options,
        renameFilter,
        template,
      }),
    { concurrency: 8 },
  );

  queue.push(svgPaths);
  await queue.wait({ empty: true });

  let legacyFiles = await globAsync(normalizePath(path.join(currentDirectory, '/legacy', '*.js')));
  legacyFiles = legacyFiles.map((file) => path.basename(file));
  let generatedFiles = await globAsync(normalizePath(path.join(options.outputDir, '*.js')));
  generatedFiles = generatedFiles.map((file) => path.basename(file));

  const duplicatedIconsLegacy = intersection(legacyFiles, generatedFiles);
  if (duplicatedIconsLegacy.length > 0) {
    throw new Error(
      `Duplicated icons in legacy folder. Either \n` +
        `1. Remove these from the /legacy folder\n` +
        `2. Add them to the blacklist to keep the legacy version\n` +
        `The following icons are duplicated: \n${duplicatedIconsLegacy.join('\n')}`,
    );
  }

  await fs.cp(path.join(currentDirectory, '/legacy'), options.outputDir, { recursive: true });
  await fs.cp(path.join(currentDirectory, '/custom'), options.outputDir, { recursive: true });

  await generateIndex(options);
}

const nodePath = path.resolve(process.argv[1]);
const modulePath = path.resolve(fileURLToPath(import.meta.url));
const isRunningDirectlyViaCLI = nodePath === modulePath;

if (isRunningDirectlyViaCLI) {

View on GitHub (pinned to bdc96df2cb)

Solutions

  1. If the generated version is now authoritative, delete the matching file(s) from the legacy/ folder.
  2. If the legacy override must be kept, add the icon name to the blacklist so generation skips it (see the existing blacklist mechanism in builder.mjs/options).
  3. Re-run the build to confirm the intersection is empty.

Example fix

// before: legacy/Star.js exists and a generated Star.js is produced
// option A — delete legacy override
rimraf packages/mui-icons-material/src/legacy/Star.js
// option B — blacklist it so only the legacy version ships (per the message)
Defensive patterns

Strategy: validation

Validate before calling

import { globby } from 'globby';
import path from 'path';
async function findDuplicates(legacyDir, outputDir) {
  const [legacy, generated] = await Promise.all([globby('*.js', { cwd: legacyDir }), globby('*.js', { cwd: outputDir })]);
  return legacy.filter(f => generated.includes(f));
}
// bail before the build if duplicates is non-empty

Prevention

When it happens

Trigger: An icon added to the upstream Material Icons set whose name matches an existing file in packages/mui-icons-material/src/legacy; renaming a legacy file to collide with a generated name; running the build after pulling new upstream icons.

Common situations: Upstream Google added an icon whose name a legacy override already used; legacy folder left with stale files after a name normalisation change.

Related errors


AI-assisted analysis of mui/material-ui@bdc96df2cb (2026-08-12). Data as JSON: /api/errors/c09d60fc3a380290. Report an issue: GitHub.