{"record":{"id":"da19ec86a01d4549","repo":"rust-lang/mdBook","slug":"duplicate-file-in-summary-md","errorCode":null,"errorMessage":"Duplicate file in SUMMARY.md: {:?}","messagePattern":"Duplicate file in SUMMARY\\.md: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/mdbook-summary/src/lib.rs","lineNumber":280,"sourceCode":"\n        Ok(Summary {\n            title,\n            prefix_chapters,\n            numbered_chapters,\n            suffix_chapters,\n        })\n    }\n\n    /// Recursively check for duplicate files in the summary items.\n    fn check_for_duplicates<'b>(\n        items: &'b [SummaryItem],\n        files: &mut HashSet<&'b PathBuf>,\n    ) -> Result<()> {\n        for item in items {\n            if let SummaryItem::Link(link) = item {\n                if let Some(location) = &link.location {\n                    if !files.insert(location) {\n                        bail!(anyhow::anyhow!(\n                            \"Duplicate file in SUMMARY.md: {:?}\",\n                            location\n                        ));\n                    }\n                }\n                // Recursively check nested items\n                Self::check_for_duplicates(&link.nested_items, files)?;\n            }\n        }\n        Ok(())\n    }\n\n    /// Parse the affix chapters.\n    fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {\n        let mut items = Vec::new();\n        debug!(\n            \"Parsing {} items\",\n            if is_prefix { \"prefix\" } else { \"suffix\" }","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/rust-lang/mdBook/blob/dc21064fc21d955a0e1f67e65e336883c3e5260b/crates/mdbook-summary/src/lib.rs#L262-L298","documentation":"While parsing SUMMARY.md, mdBook tracks every link's source file location in a set; check_for_duplicates fails the build if the same source file is referenced by more than one SUMMARY item. Each chapter source file may appear only once in the table of contents.","triggerScenarios":"Two or more Link items in SUMMARY.md (including nested/draft-part structures) point to the same .md file via their location; check_for_duplicates runs after parsing and raises this error on the second insertion.","commonSituations":"Copy-pasting a chapter line to create a second TOC entry; listing the same file as both a parent and a nested child; merging SUMMARY.md branches that both include one file.","solutions":["Remove or rename the duplicate entry in SUMMARY.md so each .md file appears once.","Create a new .md file for the second entry and link to that instead.","Use a draft chapter (empty link) as a placeholder if you only need a heading without a file."],"exampleFix":"<!-- before: SUMMARY.md -->\n- [Intro](intro.md)\n- [Intro again](intro.md)\n\n<!-- after -->\n- [Intro](intro.md)\n- [Overview](overview.md)","handlingStrategy":"validation","validationCode":"// Lint SUMMARY.md for duplicate links before building\nconst links = summary.match(/\\]\\(([^)#]+)\\)/g).map(s => s.slice(2, -1));\nconst dupes = links.filter((l, i) => links.indexOf(l) !== i);\nif (dupes.length) throw new Error(`Duplicate files: ${dupes}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add a CI lint that extracts SUMMARY.md links and fails on duplicates.","Create a new .md file rather than re-linking an existing one."],"tags":["summary","duplicate","parse","mdbook"],"backgroundTag":"duplicate-source-file-in-summary","analyzedSha":"dc21064fc21d955a0e1f67e65e336883c3e5260b","analyzedAt":"2026-09-01T10:15:12.134Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}