{"record":{"id":"04cd77465d3b5a3e","repo":"twentyhq/twenty","slug":"output-file-must-be-a-docx-pptx-or-xlsx-fil","errorCode":null,"errorMessage":"{output_file} must be a .docx, .pptx, or .xlsx file","messagePattern":"(.+?) must be a \\.docx, \\.pptx, or \\.xlsx file","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/twenty-server/src/engine/core-modules/code-interpreter/sandbox-scripts/docx/pack.py","lineNumber":62,"sourceCode":"\ndef pack_document(input_dir, output_file, validate=False):\n    \"\"\"Pack a directory into an Office file (.docx/.pptx/.xlsx).\n\n    Args:\n        input_dir: Path to unpacked Office document directory\n        output_file: Path to output Office file\n        validate: If True, validates with soffice (default: False)\n\n    Returns:\n        bool: True if successful, False if validation failed\n    \"\"\"\n    input_dir = Path(input_dir)\n    output_file = Path(output_file)\n\n    if not input_dir.is_dir():\n        raise ValueError(f\"{input_dir} is not a directory\")\n    if output_file.suffix.lower() not in {\".docx\", \".pptx\", \".xlsx\"}:\n        raise ValueError(f\"{output_file} must be a .docx, .pptx, or .xlsx file\")\n\n    # Work in temporary directory to avoid modifying original\n    with tempfile.TemporaryDirectory() as temp_dir:\n        temp_content_dir = Path(temp_dir) / \"content\"\n        shutil.copytree(input_dir, temp_content_dir)\n\n        # Process XML files to remove pretty-printing whitespace\n        for pattern in [\"*.xml\", \"*.rels\"]:\n            for xml_file in temp_content_dir.rglob(pattern):\n                condense_xml(xml_file)\n\n        # Create final Office file as zip archive\n        output_file.parent.mkdir(parents=True, exist_ok=True)\n        with zipfile.ZipFile(output_file, \"w\", zipfile.ZIP_DEFLATED) as zf:\n            for f in temp_content_dir.rglob(\"*\"):\n                if f.is_file():\n                    zf.write(f, f.relative_to(temp_content_dir))\n","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-server/src/engine/core-modules/code-interpreter/sandbox-scripts/docx/pack.py#L44-L80","documentation":"Raised by pack.py immediately after the directory check. It enforces that the output file's suffix is one of .docx/.pptx/.xlsx (case-insensitive) before doing any zipping, because the OOXML package type must match the directory structure being packed (word/ vs ppt/ vs xl/). A wrong suffix would produce a file the consumer cannot open or would write a non-OFFICE file by mistake.","triggerScenarios":"Calling pack() with an output_file whose suffix is .doc, .ppt, .xls, .pdf, .zip, .txt, or has no suffix. Mismatched suffix vs directory type (e.g. packing a ppt/ tree to a .docx output) is NOT caught here — pack trusts the caller — but a wrong extension is.","commonSituations":"An LLM in the code-interpreter passes the original input filename through as output without normalizing the extension, or constructs the output path from a stem plus a hardcoded wrong suffix. Operators sometimes use the legacy .doc/.ppt/.xls extensions expecting equivalence.","solutions":["Use one of the three supported suffixes exactly: `.docx`, `.pptx`, or `.xlsx`.","Match the suffix to the directory type you are packing (word/ → .docx, ppt/ → .pptx, xl/ → .xlsx).","If you need a legacy format, pack to .docx first then convert with soffice, rather than renaming."],"exampleFix":"# before\npack('/tmp/unpacked', '/tmp/out.doc')   # legacy .doc rejected\n# after\npack('/tmp/unpacked', '/tmp/out.docx')","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\nSUPPORTED = {'.docx', '.pptx', '.xlsx'}\n\ndef normalize_output(path: str) -> str:\n    p = Path(path)\n    if p.suffix.lower() not in SUPPORTED:\n        raise ValueError(f'output suffix must be one of {sorted(SUPPORTED)}, got {p.suffix}')\n    return str(p)","typeGuard":"from pathlib import Path\n\ndef has_office_suffix(path: str) -> bool:\n    return Path(path).suffix.lower() in {'.docx', '.pptx', '.xlsx'}","tryCatchPattern":"try:\n    pack(input_dir, output_file)\nexcept ValueError as e:\n    if 'must be a .docx' in str(e):\n        # append a correct suffix based on the directory type and retry\n        suffix = '.docx' if (Path(input_dir) / 'word').exists() else '.pptx' if (Path(input_dir) / 'ppt').exists() else '.xlsx'\n        output_file = str(Path(output_file).with_suffix(suffix))\n        pack(input_dir, output_file)\n    else:\n        raise","preventionTips":["Construct output paths from the directory type: word/ → .docx, ppt/ → .pptx, xl/ → .xlsx.","Validate the suffix before calling pack rather than relying on the throw.","Do not use legacy .doc/.ppt/.xls; convert with soffice after packing to .docx/.pptx/.xlsx."],"tags":["python","office-doc","code-interpreter","validation","file-extension"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}