anthropics/skills · error · RefusedToClean
<p:sldIdLst> lists {len(listed)} slide(s) and none of the {l
Error message
<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. What it means
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.
Source
Thrown at skills/pptx/scripts/clean.py:86
slides_dir = unpacked_dir / "ppt" / "slides"
slides_rels_dir = slides_dir / "_rels"
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
if not slides_dir.exists():
return []
referenced_slides = get_slides_in_sldidlst(unpacked_dir)
on_disk = sorted(slides_dir.glob("slide*.xml"))
if on_disk and not any(s.name in referenced_slides for s in on_disk):
listed = re.findall(
r'<p:sldId[^>]*r:id="([^"]+)"',
(unpacked_dir / "ppt" / "presentation.xml").read_text(encoding="utf-8")
if (unpacked_dir / "ppt" / "presentation.xml").exists()
else "",
)
if listed:
raise RefusedToClean(
f"<p:sldIdLst> lists {len(listed)} slide(s) and none of the "
f"{len(on_disk)} slide(s) on disk match any of them. Refusing to "
f"delete them all — this is a parse failure, not an empty deck."
)
removed = []
for slide_file in on_disk:
if slide_file.name not in referenced_slides:
rel_path = slide_file.relative_to(unpacked_dir)
slide_file.unlink()
removed.append(str(rel_path))
rels_file = slides_rels_dir / f"{slide_file.name}.rels"
if rels_file.exists():
rels_file.unlink()
removed.append(str(rels_file.relative_to(unpacked_dir)))
View on GitHub (pinned to f6656c1256)
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.
Example fix
# before clean_unused_files(unpacked_dir) # RefusedToClean on mismatched deck # after: normalize by re-saving through LibreOffice first subprocess.run(["soffice", "--headless", "--convert-to", "pptx", "--outdir", tmp, str(pptx_path)], check=True) # re-unpack tmp/deck.pptx, then clean_unused_files(unpacked_dir)
Defensive patterns
Strategy: try-catch
Validate before calling
import re, zipfile
from pathlib import Path
def slide_lists_consistent(pptx_path: Path) -> bool:
with zipfile.ZipFile(pptx_path) as zf:
rels = zf.read("ppt/_rels/presentation.xml.rels").decode()
pres = zf.read("ppt/presentation.xml").decode()
rid_to_target = dict(re.findall(r'<Relationship[^>]*Id="([^"]+)"[^>]*Target="([^"]+)"', rels))
rids = re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres)
targets = {rid_to_target.get(r, "").rsplit("/", 1)[-1] for r in rids}
on_disk = {n.rsplit("/", 1)[-1] for n in zipfile.ZipFile(pptx_path).namelist() if re.match(r"ppt/slides/slide\d+\.xml$", n)}
return bool(on_disk & targets) or not on_disk Try / catch
try:
clean_unused_files(unpacked_dir)
except RefusedToClean as e:
log.warning("refusing to clean %s: %s — re-save deck and retry", pptx_path, e)
subprocess.run(["soffice", "--headless", "--convert-to", "pptx", "--outdir", tmp, str(pptx_path)], check=True)
# re-unpack converted file and retry once Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- no relationship in this package names a part we can resolve.
- relationship target is not a POSIX part name: {target!r}
- relationship target escapes the package: {target!r}
- relationship target resolves to nothing: {target!r}
- symlink archive entry not allowed: {m.filename!r}
AI-assisted analysis of anthropics/skills@f6656c1256 (2026-08-14).
Data as JSON: /api/errors/822319dc63f6cd1c.
Report an issue: GitHub.