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
- If the generated version is now authoritative, delete the matching file(s) from the legacy/ folder.
- 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).
- 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
- Run a duplicate-name check in a pre-build script when upstream icons are refreshed.
- Keep the legacy/ folder pruned of files that are now generated upstream.
- Document the blacklist mechanism so contributors know how to keep a legacy override.
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
- Expected a single child of the root
- Expected an svg element as the root child
- renameFilter must be a function
- docs-infra: The title "${title}" is too long (${title.length
- docs-infra: Missing description in the page: ${location}\n
AI-assisted analysis of mui/material-ui@bdc96df2cb (2026-08-12).
Data as JSON: /api/errors/c09d60fc3a380290.
Report an issue: GitHub.