twentyhq/twenty · error · ValueError
{output_file} must be a .docx, .pptx, or .xlsx file
Error message
{output_file} must be a .docx, .pptx, or .xlsx file What it means
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.
Source
Thrown at packages/twenty-server/src/engine/core-modules/code-interpreter/sandbox-scripts/docx/pack.py:62
def pack_document(input_dir, output_file, validate=False):
"""Pack a directory into an Office file (.docx/.pptx/.xlsx).
Args:
input_dir: Path to unpacked Office document directory
output_file: Path to output Office file
validate: If True, validates with soffice (default: False)
Returns:
bool: True if successful, False if validation failed
"""
input_dir = Path(input_dir)
output_file = Path(output_file)
if not input_dir.is_dir():
raise ValueError(f"{input_dir} is not a directory")
if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}:
raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file")
# Work in temporary directory to avoid modifying original
with tempfile.TemporaryDirectory() as temp_dir:
temp_content_dir = Path(temp_dir) / "content"
shutil.copytree(input_dir, temp_content_dir)
# Process XML files to remove pretty-printing whitespace
for pattern in ["*.xml", "*.rels"]:
for xml_file in temp_content_dir.rglob(pattern):
condense_xml(xml_file)
# Create final Office file as zip archive
output_file.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf:
for f in temp_content_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(temp_content_dir))
View on GitHub (pinned to 1f5dd2bbd2)
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.
Example fix
# before
pack('/tmp/unpacked', '/tmp/out.doc') # legacy .doc rejected
# after
pack('/tmp/unpacked', '/tmp/out.docx') Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
SUPPORTED = {'.docx', '.pptx', '.xlsx'}
def normalize_output(path: str) -> str:
p = Path(path)
if p.suffix.lower() not in SUPPORTED:
raise ValueError(f'output suffix must be one of {sorted(SUPPORTED)}, got {p.suffix}')
return str(p) Type guard
from pathlib import Path
def has_office_suffix(path: str) -> bool:
return Path(path).suffix.lower() in {'.docx', '.pptx', '.xlsx'} Try / catch
try:
pack(input_dir, output_file)
except ValueError as e:
if 'must be a .docx' in str(e):
# append a correct suffix based on the directory type and retry
suffix = '.docx' if (Path(input_dir) / 'word').exists() else '.pptx' if (Path(input_dir) / 'ppt').exists() else '.xlsx'
output_file = str(Path(output_file).with_suffix(suffix))
pack(input_dir, output_file)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- {input_dir} is not a directory
- Slide index {idx} out of range (0-{total_slides - 1})
- Found {len(errors)} validation error(s)
- Found {len(overflow_errors)} overflow error(s) and {len(warn
- Duplicate key found in JSON: '{key}'
AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12).
Data as JSON: /api/errors/04cd77465d3b5a3e.
Report an issue: GitHub.