PrefectHQ/fastmcp · error · StateFileError

Could not read CLI state: {path.name}

Error message

Could not read CLI state: {path.name}

What it means

StateFileError raised by read_state when path.read_text raises OSError after the existence and symlink checks pass. The file is present but unreadable at the OS level, so the CLI cannot load the state and reports the filename in the message.

Source

Thrown at fastmcp_slim/fastmcp/cli/deploy/state.py:166

    *,
    secret: bool = False,
) -> ModelT | None:
    """Read and validate a versioned JSON state file."""
    if not path.exists():
        return None
    if path.is_symlink():
        raise StateFileError(f"CLI state must not be a symbolic link: {path.name}")

    if secret:
        _restrict_access(path.parent, directory=True)
        _restrict_access(path)

    try:
        return model.model_validate_json(path.read_text(encoding="utf-8"))
    except (ValidationError, ValueError):
        raise StateFileError(f"CLI state is invalid: {path.name}") from None
    except OSError as exc:
        raise StateFileError(f"Could not read CLI state: {path.name}") from exc


def write_state(path: Path, data: dict[str, Any]) -> None:
    """Write JSON through a restricted temporary file and atomic replacement."""
    _prepare_directory(path.parent)
    payload = (json.dumps(data, indent=2, sort_keys=True) + "\n").encode()
    descriptor: int | None = None
    temporary_path: Path | None = None

    try:
        descriptor, temporary_name = tempfile.mkstemp(
            dir=path.parent,
            prefix=f".{path.name}.",
            suffix=".tmp",
        )
        temporary_path = Path(temporary_name)
        if os.name != "nt":
            os.fchmod(descriptor, 0o600)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Fix ownership/permissions on the file: chown $USER or chmod u+r on the state file (and 0o700 on its directory)
  2. Stop mixing elevated and normal runs (avoid sudo for CLI commands, or chown the state back afterwards)
  3. Delete the unreadable file and re-authenticate to regenerate it
  4. Check disk/health (dmesg) if I/O errors persist

Example fix

// before (root-owned after sudo run)
-rw------- root root ~/.fastmcp/state/session.json
// after
$ sudo chown $USER ~/.fastmcp/state/session.json
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def assert_readable(path: Path) -> None:
    if path.exists() and not os.access(path, os.R_OK):
        raise RuntimeError(f"{path} is not readable; fix ownership/permissions")

Try / catch

try:
    state = load(state_path)
except StateFileError as exc:
    if "Could not read" in str(exc):
        subprocess.run(["chown", str(os.getuid()), str(state_path)], check=False)
        state = load(state_path)
    else:
        raise

Prevention

When it happens

Trigger: read_state (via load) on a file whose permissions deny the current user read access (e.g. state created under another account, or a prior _restrict_access left it 0o600 owned by root), or an I/O error while reading (failing disk, file deleted between exists() and read).

Common situations: Running the CLI with sudo previously so the state file is now root-owned; different user account after a machine migration; EFS/encrypted-volume errors; race where another process removed the file mid-read.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/f7a12af6fa0495d8. Report an issue: GitHub.