pydantic/monty · warning · MountError

File name too long

Error message

File name too long

What it means

`MountTable::handle_os_call` rejects OS calls whose primary virtual path exceeds the filesystem's maximum name/path length with a 'File name too long' error (ENAMETOOLONG), matching CPython's OSError for the same condition. The length check runs first and is O(1) so hostile paths cannot make the null-byte scan do unbounded work. Unlike some rejections, this one surfaces as an error rather than a `False` predicate, because CPython raises `OSError` for overlong paths.

Source

Thrown at crates/monty-fs/src/mount_table.rs:113

    }

    /// Handles an OS call using the mount table.
    ///
    /// Consumes the call so a covered write's payload is *moved* into the
    /// backend (overlay storage retains it without a copy). Routing happens
    /// on a borrow first, so [`MountCallOutcome::NotHandled`] hands the call
    /// back untouched for the caller's fallback handler (a host callback or
    /// [`OsFunctionCall::on_no_handler`]).
    ///
    /// Path length and null bytes are checked before anything else touches the
    /// path, so both apply whether or not a mount covers it — as in CPython,
    /// where neither reaches a syscall.
    pub fn handle_os_call(&mut self, call: OsFunctionCall) -> MountCallOutcome {
        if let Some(primary_path) = call.fs_primary_path() {
            // Length first: it is the only check that stays O(1) on a hostile
            // path, so a null scan must not run ahead of it. A path that is
            // both reports its length, where CPython reports the null byte.
            let rejection = reject_overlong_path(primary_path).err().or_else(|| {
                contains_null_byte(primary_path)
                    .then(|| MountError::EmbeddedNullByte(call.embedded_null_message(false)))
            });
            if let Some(e) = rejection {
                // Both make CPython's predicates answer `False` rather than
                // raise — `pathlib` swallows `OSError` and `ValueError` alike.
                MountCallOutcome::Handled(if call.is_existence_check() {
                    Ok(MontyObject::Bool(false))
                } else {
                    Err(e)
                })
            } else {
                match self.route_call(primary_path, &call) {
                    Some(Ok(index)) => MountCallOutcome::Handled(self.mounts[index].execute(call)),
                    Some(Err(err)) => MountCallOutcome::Handled(Err(err)),
                    None => MountCallOutcome::NotHandled(call),
                }
            }

View on GitHub (pinned to adc986b362)

Solutions

  1. Shorten the file or directory names your sandboxed code uses (keep components under ~255 bytes and total paths well under 4096).
  2. Truncate or hash long logical names before writing (e.g. store a short digest as the filename and keep the long name in content/metadata).
  3. Restructure deep output trees into shallower directories to reduce total path length.

Example fix

// before
name = 'log-' + '-'.join(entries)  # hundreds of chars
open(name, 'w')
// after
import hashlib
name = 'log-' + hashlib.sha256('-'.join(entries).encode()).hexdigest()[:32]
open(name, 'w')
Defensive patterns

Strategy: validation

Validate before calling

import os

def path_fits(path: str, max_total: int = 4096, max_component: int = 255) -> bool:
    return len(path.encode()) <= max_total and all(
        len(part.encode()) <= max_component for part in path.split('/')
    )

if not path_fits(my_virtual_path):
    raise ValueError('path too long for mounted filesystem')

Type guard

def is_safe_mounted_path(path: str) -> bool:
    return (
        '\x00' not in path
        and len(path.encode()) <= 4096
        and all(len(p.encode()) <= 255 for p in path.split('/'))
    )

Try / catch

try:
    result = session.feed_run(f"open({virtual_path!r})")
except MontyRuntimeError as exc:
    if 'File name too long' in str(exc):
        # shorten/hash the filename and retry
        ...
    raise

Prevention

When it happens

Trigger: Calling any file operation (open, rename, stat, list, mkdir, etc.) through a mount whose virtual path component or full path exceeds the host maximum (commonly NAME_MAX=255 bytes per component or PATH_MAX=4096 total) — e.g. `open('a'*300)` inside the sandbox, or `rename` with an extremely long destination.

Common situations: Generated filenames from untrusted input (hashes concatenated, templated names), deep directory trees pushing total path length past PATH_MAX, or rename operations with one side of the path outside the mount being validated first.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/2431038b01e56615. Report an issue: GitHub.