FileNotFoundError por ruta larga en Windows (MAX_PATH)

Un script de Python escribe un archivo en una carpeta que existe y falla igualmente:

FileNotFoundError: [Errno 2] No such file or directory: 'C:\\proyectos\\mi-app\\tmp\\sesion\\resultados\\...\\informe_final.xlsx'

En Node.js, una herramienta que lanza procesos desde una carpeta muy anidada puede abortar con un mensaje como este (el texto exacto depende de la herramienta):

Error: Current working directory has a path longer than allowed for a Win32 working directory

El patrón siempre es el mismo: todo funciona en carpetas cortas y falla solo en carpetas temporales o de sesión muy anidadas. No falta nada: la ruta completa supera el límite clásico de Windows de 260 caracteres. Este artículo explica cómo confirmarlo, qué soluciones hay y qué cubre cada una.

Por qué pasa

En la API de Windows, la longitud máxima de una ruta es MAX_PATH, definida en 260 caracteres. Esa cifra incluye la letra de unidad, los dos puntos, la barra inicial, los nombres separados por barras y el carácter nulo final, así que en la práctica quedan unos 259 caracteres. Una carpeta base larga más nombres de archivo largos (títulos de informes, identificadores, fechas) suman más de 260 con facilidad.

Cuando se supera el límite, Windows responde "ruta no encontrada" y Python lo traduce a FileNotFoundError, aunque la carpeta exista. Con rutas largas desactivadas, os.path.isdir() devuelve False sobre una carpeta que sí existe, y os.makedirs() falla con [WinError 3] ("El sistema no puede encontrar la ruta especificada" en Windows en español). Cualquier biblioteca que escriba en esa ruta con open() fallará igual: guardar un libro de Excel con openpyxl, una imagen con Pillow o una figura con savefig de matplotlib.

El directorio de trabajo tiene además su propio límite. La documentación de SetCurrentDirectory indica que, por defecto, el nombre está limitado a MAX_PATH caracteres y que fijar un directorio actual más largo hace fallar CreateProcessW, la función con la que Windows crea procesos. Por eso lanzar un subproceso con un cwd largo falla incluso en programas que sí pueden escribir archivos con rutas largas.

Desde Windows 10 versión 1607 el límite puede levantarse, pero es opcional por aplicación: según Microsoft, hacen falta dos condiciones. El valor de registro LongPathsEnabled debe estar en 1 y la aplicación debe declarar longPathAware en su manifiesto. Activar el valor de registro no afecta a las aplicaciones que no lo declaran, y el sistema lo lee una vez por proceso, por lo que puede ser necesario reiniciar.

Solución

  1. Localiza las rutas largas. Este script lista los archivos de una carpeta cuya ruta tiene 250 caracteres o más. Usa el prefijo \\?\ para poder recorrer lo que una ruta normal no deja abrir:

    import os
    import sys
    
    def rutas_largas(raiz, umbral=250):
        for carpeta, _, archivos in os.walk("\\\\?\\" + os.path.abspath(raiz)):
            for nombre in archivos:
                ruta = os.path.join(carpeta, nombre)[4:]  # quita el prefijo \\?\
                if len(ruta) >= umbral:
                    yield len(ruta), ruta
    
    for largo, ruta in rutas_largas(sys.argv[1]):
        print(largo, ruta)
  2. Trabaja desde una ruta base corta. Es la solución más fiable porque no depende de ninguna herramienta. Crea una carpeta como C:\tmp\trabajo, genera ahí los archivos intermedios y copia al destino definitivo solo el resultado final. Muchas herramientas toman la carpeta temporal de las variables TMP y TEMP (la función GetTempPath de Windows las consulta en ese orden), así que en PowerShell puedes redirigirlas para esa sesión:

    New-Item -ItemType Directory -Force C:\tmp\trabajo | Out-Null
    $env:TMP = "C:\tmp\trabajo"
    $env:TEMP = "C:\tmp\trabajo"

    Qué cubre: todo, incluido el directorio de trabajo, cmd.exe y las herramientas que no admiten rutas largas.

  3. Acorta los nombres que genera tu código. Usa identificadores cortos y guarda la correspondencia con el nombre largo en un archivo de manifiesto:

    import csv
    from pathlib import Path
    
    def guardar_lote(carpeta: Path, items: list[tuple[str, bytes]]) -> None:
        carpeta.mkdir(parents=True, exist_ok=True)
        with (carpeta / "manifiesto.csv").open("w", newline="", encoding="utf-8") as m:
            w = csv.writer(m)
            w.writerow(["id", "nombre_original"])
            for n, (nombre, datos) in enumerate(items, start=1):
                id_corto = f"{n:04d}"
                (carpeta / f"{id_corto}.bin").write_bytes(datos)
                w.writerow([id_corto, nombre])

    Qué cubre: ataca la causa (la longitud total) y funciona en cualquier equipo, sin tocar la configuración de Windows.

  4. Activa las rutas largas en Windows. En una consola de PowerShell con permisos de administrador:

    New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
      -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force

    También puede activarse con la directiva de grupo "Enable Win32 long paths" (Configuración del equipo, Plantillas administrativas, Sistema, Sistema de archivos). Reinicia el equipo: Microsoft indica que puede ser necesario porque algunos procesos pudieron arrancar antes del cambio. La documentación de Python para Windows indica que, con este valor activado y tras reiniciar, open(), el módulo os y la mayoría de las funciones de rutas aceptan rutas de más de 260 caracteres sin más configuración.

    Git tiene su propio ajuste. Con --system escribe en la configuración del sistema, por lo que normalmente requiere permisos de administrador:

    git config --system core.longpaths true

    Qué cubre: las aplicaciones que declaran longPathAware (Python, según su documentación) y los comandos de Git escritos en C. No cubre las herramientas que no lo declaran. Git for Windows advierte además que los comandos de Git implementados como scripts pueden seguir fallando.

  5. Usa el prefijo \\?\ en el código que controlas. Para las funciones de la API que lo admiten, una ruta absoluta con este prefijo se pasa al sistema con modificaciones mínimas:

    import os
    
    ruta = r"C:\tmp\trabajo\una\carpeta\muy\larga\informe.txt"
    with open("\\\\?\\" + os.path.abspath(ruta), "w", encoding="utf-8") as f:
        f.write("ok")

    Microsoft precisa que con este prefijo no puedes usar barras normales, ni . ni .., y que las rutas relativas siguen limitadas a MAX_PATH. Qué cubre: solo el código propio que llama a funciones compatibles; no sirve como argumento para otros programas ni para el directorio de trabajo.

Cómo verificar que quedó resuelto

Si activaste las rutas largas, comprueba el valor de registro:

Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name LongPathsEnabled

Debe mostrar LongPathsEnabled : 1. Después prueba una ruta que supere los 260 caracteres sin el prefijo:

from pathlib import Path

ruta = Path(r"C:\tmp\trabajo") / ("a" * 120) / ("b" * 120) / "prueba.txt"
print(len(str(ruta)))
try:
    ruta.parent.mkdir(parents=True, exist_ok=True)
    ruta.write_text("ok", encoding="utf-8")
    print("OK: la ruta larga funciona")
except FileNotFoundError as e:
    print("FALLA:", e)

Con rutas largas desactivadas debe imprimir FALLA; con ellas activas (y tras reiniciar), OK. Si optaste por la ruta corta o los nombres cortos, vuelve a ejecutar el script original desde C:\tmp\trabajo: debe guardar los gráficos y el libro de Excel sin FileNotFoundError, y el libro debe abrirse con normalidad.

Variantes y errores relacionados

Node.js y subprocesos

El síntoma cambia según la API. En una prueba con Node.js 24 en Windows 11 con rutas largas desactivadas, process.chdir() falló con ENAMETOOLONG y child_process.spawnSync con un cwd largo falló con un engañoso spawnSync cmd ENOENT, mientras que fs.writeFileSync con una ruta absoluta larga funcionó. En Python, subprocess.run(..., cwd=ruta_larga) lanzó NotADirectoryError: [WinError 267] ("El nombre del directorio no es válido"). Es un comportamiento observado que puede variar entre versiones; la solución es la misma: ejecuta desde una ruta corta.

Git: Filename too long

Al clonar un repositorio con nombres largos dentro de una carpeta que ya es larga, Git puede informar de que no puede crear un archivo porque el nombre es demasiado largo (Filename too long). Microsoft menciona justo este caso. Clona en una carpeta corta como C:\src o activa core.longpaths.

El límite de los directorios es menor

Al crear un directorio con la API, la ruta no puede ser tan larga que no quepa después un nombre 8.3: el nombre del directorio no puede superar MAX_PATH menos 12 caracteres. Puedes encontrar errores con rutas de 250 caracteres.

Explorador, cmd.exe y otras aplicaciones

Aunque tu script funcione, el Explorador de archivos, cmd.exe y muchas otras aplicaciones (incluidas las utilidades que ejecutas desde Git Bash, si no declaran soporte) pueden seguir sin admitir rutas largas. Microsoft señala que el shell y el sistema de archivos tienen requisitos distintos, y que activar el registro solo afecta a las aplicaciones preparadas.

Resumen

  • Un FileNotFoundError con la carpeta existente y una ruta de más de 260 caracteres apunta al límite MAX_PATH de Windows.
  • El directorio de trabajo tiene su propio límite: un cwd largo hace fallar la creación del proceso.
  • Lo más fiable es trabajar desde una ruta corta (C:\tmp\trabajo) y usar nombres cortos con un manifiesto.
  • LongPathsEnabled y core.longpaths ayudan solo a las aplicaciones y comandos que lo admiten; el prefijo \\?\ solo al código que lo usa.
  • Verifica con el valor de registro y una prueba que cree un archivo en una ruta de más de 260 caracteres.

Referencias

Comentarios