{"record":{"id":"70b071be06bb7d95","repo":"anthropics/skills","slug":"word-not-found-not-an-unpacked-docx","errorCode":null,"errorMessage":"{word} not found (not an unpacked .docx?)","messagePattern":"(.+?) not found \\(not an unpacked \\.docx\\?\\)","errorType":"exception","errorClass":"FileNotFoundError","httpStatus":null,"severity":"error","filePath":"skills/docx/scripts/comment.py","lineNumber":251,"sourceCode":"\n\ndef add_comment(\n    unpacked_dir: Path | str,\n    text: str,\n    comment_id: int | None = None,\n    author: str = \"Claude\",\n    initials: str = \"C\",\n    parent_id: int | None = None,\n    raw: bool = False,\n) -> tuple[int, str, str]:\n    unpacked_dir = Path(unpacked_dir)\n    if not raw:\n        text = xml_escape(text)\n    author = xml_escape(author, {'\"': \"&quot;\"})\n    initials = xml_escape(initials, {'\"': \"&quot;\"})\n    word = unpacked_dir / \"word\"\n    if not word.exists():\n        raise FileNotFoundError(f\"{word} not found (not an unpacked .docx?)\")\n\n    comments = word / \"comments.xml\"\n    if comment_id is None:\n        comment_id = _next_comment_id(comments)\n\n    parent_para = None\n    if parent_id is not None:\n        parent_para = _find_para_id(comments, parent_id) if comments.exists() else None\n        if not parent_para:\n            raise ValueError(f\"parent comment {parent_id} not found\")\n\n    para_id, durable_id = _generate_hex_id(), _generate_hex_id()\n    ts = datetime.now(timezone.utc).strftime(\"%Y-%m-%dT%H:%M:%SZ\")\n\n    if not comments.exists():\n        shutil.copy(TEMPLATE_DIR / \"comments.xml\", comments)\n    _ensure_comment_relationships(unpacked_dir)\n    _ensure_comment_content_types(unpacked_dir)","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/anthropics/skills/blob/f6656c1256d5a8adfa37db9110046ef20bac644c/skills/docx/scripts/comment.py#L233-L269","documentation":"Raised by add_comment() (and its CLI wrapper in comment.py) when the unpacked directory does not contain a word/ subdirectory, which every real unpacked .docx must have. It signals the caller passed a directory that is not an unpacked OOXML package — e.g. a zip extracted from something other than a DOCX, or the parent of the unpacked tree.","triggerScenarios":"Calling add_comment(unpacked_dir=...) (or the comment.py CLI with --raw/--unpacked-style flow) where unpacked_dir lacks a 'word' child: passing the .docx file path itself instead of the unpacked directory, passing the zip's root when it contains only 'ppt/' or 'xl/', or passing a directory that was never unpacked.","commonSituations":"Forgetting to run the unpack step before commenting; operating on PPTX/XLSX files with the docx tooling; passing the directory that CONTAINS the unpacked tree (one level too high); a previous unpack step failed silently leaving an empty directory.","solutions":["Unpack the DOCX first (e.g. 'unzip file.docx -d file_unpacked') and pass that directory so that file_unpacked/word exists.","Check you passed the unpacked directory, not the .docx file path and not the parent of the unpacked tree.","Confirm the source file is actually a DOCX ('unzip -l file.docx | grep word/document.xml').","If using a custom unpack helper, verify it preserved internal structure rather than flattening entries."],"exampleFix":"# before\nadd_comment(unpacked_dir=Path(\"report.docx\"), text=\"note\")  # FileNotFoundError\n\n# after\nimport zipfile, tempfile\nwith zipfile.ZipFile(\"report.docx\") as zf, tempfile.TemporaryDirectory() as td:\n    zf.extractall(td)\n    add_comment(unpacked_dir=Path(td), text=\"note\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_unpacked_docx(unpacked_dir: str | Path) -> bool:\n    return (Path(unpacked_dir) / \"word\").is_dir() and (Path(unpacked_dir) / \"word\" / \"document.xml\").exists()","typeGuard":null,"tryCatchPattern":"from pathlib import Path\ntry:\n    add_comment(unpacked_dir=d, text=\"note\")\nexcept FileNotFoundError as e:\n    if \"not an unpacked .docx\" in str(e):\n        raise RuntimeError(f\"unpack {d} first: expected {d}/word to exist\") from e\n    raise","preventionTips":["Always unpack to a dedicated temp directory and pass that directory.","Assert (dir / 'word').is_dir() before calling docx helpers.","Keep per-format tools separate: never point docx scripts at pptx/xlsx trees.","Verify the unpack step succeeded (entry count > 0) before further processing."],"tags":["docx","filesystem","validation","ooxml"],"backgroundTag":null,"analyzedSha":"f6656c1256d5a8adfa37db9110046ef20bac644c","analyzedAt":"2026-08-14T16:09:17.493Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}