twentyhq/twenty · error · ValueError

{input_dir} is not a directory

Error message

{input_dir} is not a directory

What it means

Raised by pack.py's pack function, which re-zips an unpacked Office document directory back into a .docx/.pptx/.xlsx file. It guards input_dir.is_dir() before copying the tree into a temp working directory. The function expects the on-disk layout produced by the matching unpack.py (the OOXML directory structure with [Content_Types].xml, _rels/, word|ppt|xl/).

Source

Thrown at packages/twenty-server/src/engine/core-modules/code-interpreter/sandbox-scripts/docx/pack.py:60

        sys.exit(f"Error: {e}")


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():

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. Verify the path exists and is a directory before calling pack: `Path(input_dir).is_dir()`.
  2. Ensure the preceding unpack step succeeded and returned the directory it created.
  3. Pass an absolute path to avoid cwd-relative ambiguity inside the sandbox.
  4. Confirm the directory contains [Content_Types].xml — if not, it is not a valid unpacked OOXML tree.

Example fix

# before
pack('/tmp/out.docx', '/tmp/result.docx')   # passing a file, not the unpacked dir
# after
pack('/tmp/unpacked_out.docx', '/tmp/result.docx')   # the directory unpack() produced
Defensive patterns

Strategy: type-guard

Validate before calling

from pathlib import Path

def safe_pack(input_dir, output_file):
    p = Path(input_dir)
    if not p.exists():
        raise FileNotFoundError(f'Input path does not exist: {input_dir}')
    if not p.is_dir():
        raise NotADirectoryError(f'Input path is not a directory: {input_dir}')
    if not (p / '[Content_Types].xml').exists():
        raise ValueError(f'Not a valid OOXML tree (missing [Content_Types].xml): {input_dir}')
    return pack(input_dir, output_file)

Type guard

from pathlib import Path

def is_unpacked_ooxml_dir(path: str) -> bool:
    p = Path(path)
    return p.is_dir() and (p / '[Content_Types].xml').exists()

Try / catch

from pathlib import Path
try:
    pack(input_dir, output_file)
except ValueError as e:
    if 'is not a directory' in str(e):
        # recover: maybe caller passed the .docx; try unpacking first
        if Path(input_dir).is_file():
            unpacked = unpack(input_dir, tempfile.mkdtemp())
            pack(unpacked, output_file)
        else:
            raise
    else:
        raise

Prevention

When it happens

Trigger: Calling pack() with a path that does not exist, points to a regular file (e.g. passing a .docx instead of its unpacked directory), or points to a directory that was never created because a prior unpack step failed silently.

Common situations: Code-interpreter sandbox pipeline where an LLM-generated script calls unpack then pack but the unpack step crashed, leaving no directory; or a path-joining bug (e.g. packing the output file path rather than the directory). Also when the working directory assumption differs between the caller and pack.py.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/d391057a050e9586. Report an issue: GitHub.