langflow-ai/langflow · error · HTTPException

Failed to write flow to filesystem: {e}

Error message

Failed to write flow to filesystem: {e}

What it means

HTTP 500 from _save_flow_to_fs: an OSError occurred while writing the flow JSON to the validated filesystem path (mkdir of parents, aiofiles.open for write, or the write itself). The original HTTPExceptions from path validation are re-raised untouched; this branch is purely I/O failure after validation passed, and the exception is logged with the flow name and path via logger.aexception.

Source

Thrown at src/backend/base/langflow/api/v1/flows_helpers.py:195

        if not await safe_path.exists():
            await safe_path.touch()


async def _save_flow_to_fs(flow: Flow, user_id: UUID, storage_service: StorageService) -> None:
    """Save flow data to the filesystem at the validated path."""
    if not flow.fs_path:
        return

    try:
        safe_path = _get_safe_flow_path(flow.fs_path, user_id, storage_service)
        await safe_path.parent.mkdir(parents=True, exist_ok=True)
        async with aiofiles.open(str(safe_path), "w") as f:
            await f.write(flow.model_dump_json())
    except HTTPException:
        raise
    except OSError as e:
        await logger.aexception("Failed to write flow %s to path %s", flow.name, flow.fs_path)
        raise HTTPException(status_code=500, detail=f"Failed to write flow to filesystem: {e}") from e


async def _deduplicate_flow_name(session: AsyncSession, name: str, user_id: UUID) -> str:
    """Return a unique flow name for *user_id*, appending ``(N)`` if needed."""
    if not (await session.exec(select(Flow).where(Flow.name == name).where(Flow.user_id == user_id))).first():
        return name

    flows = (
        await session.exec(
            select(Flow).where(Flow.name.like(f"{name} (%")).where(Flow.user_id == user_id)  # type: ignore[attr-defined]
        )
    ).all()

    # Extract copy-number suffixes: "MyFlow (2)" → 2
    extract_number = re.compile(rf"^{re.escape(name)} \((\d+)\)$")
    numbers = [int(m.group(1)) for f in flows if (m := extract_number.search(f.name))]

    return f"{name} ({max(numbers) + 1})" if numbers else f"{name} (1)"

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Check disk space (df -h) and permissions on <data_dir>/flows/<user_id> for the backend user.
  2. In containers, ensure the mounted volume is writable by the container user (chown/chmod or runAsUser match).
  3. Remove any directory that collides with the target filename.
  4. Read the server log: the aexception line names the flow and fs_path that failed.

Example fix

# before: read-only volume
docker run -v /srv/flows:/data/flows:ro ...
# after
docker run -v /srv/flows:/data/flows:rw ...
Defensive patterns

Strategy: fallback

Validate before calling

# pre-flight on the server
import os; d = f"{DATA_DIR}/flows/{user_id}"; assert os.access(d, os.W_OK), 'flows dir not writable'

Try / catch

try { await saveFlow(body) } catch (e) { if (e.status === 500 && 'filesystem' in e.detail) { alertOpsDisk(); body = {...body, fs_path: null}; return await saveFlow(body); } throw e; }

Prevention

When it happens

Trigger: Flow create/update with a valid fs_path where the server cannot write: disk full, permission denied on <data_dir>/flows/<user_id>, directory replaced by a read-only mount, or the file is a directory at that name.

Common situations: Docker container where the data volume is read-only or owned by a different uid; disk exhaustion; SELinux/AppArmor denying writes; an existing directory named exactly like the target .json file.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/9a16edaa2e6ca73a. Report an issue: GitHub.