{"record":{"id":"822319dc63f6cd1c","repo":"anthropics/skills","slug":"p-sldidlst-lists-len-listed-slide-s-and-none","errorCode":null,"errorMessage":"<p:sldIdLst> lists {len(listed)} slide(s) and none of the {len(on_disk)} slide(s) on disk match any of them. Refusing to delete them all — this is a parse failure, not an empty deck.","messagePattern":"<p:sldIdLst> lists (.+?) slide\\(s\\) and none of the (.+?) slide\\(s\\) on disk match any of them\\. Refusing to delete them all — this is a parse failure, not an empty deck\\.","errorType":"exception","errorClass":"RefusedToClean","httpStatus":null,"severity":"error","filePath":"skills/pptx/scripts/clean.py","lineNumber":86,"sourceCode":"    slides_dir = unpacked_dir / \"ppt\" / \"slides\"\n    slides_rels_dir = slides_dir / \"_rels\"\n    pres_rels_path = unpacked_dir / \"ppt\" / \"_rels\" / \"presentation.xml.rels\"\n\n    if not slides_dir.exists():\n        return []\n\n    referenced_slides = get_slides_in_sldidlst(unpacked_dir)\n    on_disk = sorted(slides_dir.glob(\"slide*.xml\"))\n\n    if on_disk and not any(s.name in referenced_slides for s in on_disk):\n        listed = re.findall(\n            r'<p:sldId[^>]*r:id=\"([^\"]+)\"',\n            (unpacked_dir / \"ppt\" / \"presentation.xml\").read_text(encoding=\"utf-8\")\n            if (unpacked_dir / \"ppt\" / \"presentation.xml\").exists()\n            else \"\",\n        )\n        if listed:\n            raise RefusedToClean(\n                f\"<p:sldIdLst> lists {len(listed)} slide(s) and none of the \"\n                f\"{len(on_disk)} slide(s) on disk match any of them. Refusing to \"\n                f\"delete them all — this is a parse failure, not an empty deck.\"\n            )\n\n    removed = []\n\n    for slide_file in on_disk:\n        if slide_file.name not in referenced_slides:\n            rel_path = slide_file.relative_to(unpacked_dir)\n            slide_file.unlink()\n            removed.append(str(rel_path))\n\n            rels_file = slides_rels_dir / f\"{slide_file.name}.rels\"\n            if rels_file.exists():\n                rels_file.unlink()\n                removed.append(str(rels_file.relative_to(unpacked_dir)))\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/anthropics/skills/blob/f6656c1256d5a8adfa37db9110046ef20bac644c/skills/pptx/scripts/clean.py#L68-L104","documentation":"remove_orphaned_slides() in pptx/scripts/clean.py cross-checks the slide files on disk (ppt/slides/slide*.xml) against the r:id list in ppt/presentation.xml's <p:sldIdLst>. If slides exist on disk but ZERO of them match the listed r:ids, the mismatch is almost certainly a parsing failure (relationship IDs resolved to different part names than expected), so it refuses to run rather than delete every slide. This is a safety interlock against data-destroying false positives.","triggerScenarios":"Cleaning a deck where get_slides_in_sldidlst() returns part names that share no overlap with the actual slideN.xml filenames — e.g. nonstandard part naming (slides/slide1.xml vs ppt/slides/slide1.xml resolution differences), rels using unusual targets, or presentation.xml r:ids that map through a remapped .rels file. Requires listed to be non-empty (sldIdLst parsed but nothing matched).","commonSituations":"Decks from third-party generators with unusual relationship structures; packages whose presentation.xml.rels targets use absolute or oddly-cased part names; partially corrupted decks after bad round-trips.","solutions":["Do not force-clean: open the deck in PowerPoint/LibreOffice, re-save it, and retry — a conformant rewrite fixes rel/part naming.","Compare the r:id list in ppt/presentation.xml <p:sldIdLst> with Targets in ppt/_rels/presentation.xml.rels to find why resolution mismatches (case, leading slash, subfolder).","Check for a stale unpacked tree: re-unpack the pptx fresh before cleaning.","If the deck is genuinely empty-of-references, verify manually and only then delete slides with an explicit custom step."],"exampleFix":"# before\nclean_unused_files(unpacked_dir)  # RefusedToClean on mismatched deck\n\n# after: normalize by re-saving through LibreOffice first\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pptx\", \"--outdir\", tmp, str(pptx_path)], check=True)\n# re-unpack tmp/deck.pptx, then clean_unused_files(unpacked_dir)","handlingStrategy":"try-catch","validationCode":"import re, zipfile\nfrom pathlib import Path\n\ndef slide_lists_consistent(pptx_path: Path) -> bool:\n    with zipfile.ZipFile(pptx_path) as zf:\n        rels = zf.read(\"ppt/_rels/presentation.xml.rels\").decode()\n        pres = zf.read(\"ppt/presentation.xml\").decode()\n    rid_to_target = dict(re.findall(r'<Relationship[^>]*Id=\"([^\"]+)\"[^>]*Target=\"([^\"]+)\"', rels))\n    rids = re.findall(r'<p:sldId[^>]*r:id=\"([^\"]+)\"', pres)\n    targets = {rid_to_target.get(r, \"\").rsplit(\"/\", 1)[-1] for r in rids}\n    on_disk = {n.rsplit(\"/\", 1)[-1] for n in zipfile.ZipFile(pptx_path).namelist() if re.match(r\"ppt/slides/slide\\d+\\.xml$\", n)}\n    return bool(on_disk & targets) or not on_disk","typeGuard":null,"tryCatchPattern":"try:\n    clean_unused_files(unpacked_dir)\nexcept RefusedToClean as e:\n    log.warning(\"refusing to clean %s: %s — re-save deck and retry\", pptx_path, e)\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pptx\", \"--outdir\", tmp, str(pptx_path)], check=True)\n    # re-unpack converted file and retry once","preventionTips":["Re-save third-party decks through LibreOffice/PowerPoint before automated cleaning.","Always keep the original pptx — clean.py deletions are destructive.","Verify the sldIdLst-to-part mapping resolves before running cleaners.","Never bypass the RefusedToClean guard by deleting slides manually."],"tags":["pptx","ooxml","safety","cleaning","slides"],"backgroundTag":null,"analyzedSha":"f6656c1256d5a8adfa37db9110046ef20bac644c","analyzedAt":"2026-08-14T16:09:17.493Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}