janhq/jan · error · ServerError::Io

IO error: {0}

Error message

IO error: {0}

What it means

`ServerError::Io` wraps any `std::io::Error` propagated out of the MLX plugin (via `#[from]`). It fires when the plugin itself hits an I/O problem — not the MLX subprocess: e.g. the model file cannot be opened, a temp file cannot be written, or a pipe to the subprocess cannot be created. The custom `Serialize` impl converts this variant into an `MlxError` with `ErrorCode::IoError` before serializing.

Source

Thrown at src-tauri/plugins/tauri-plugin-mlx/src/error.rs:62

                "Out of memory. The model requires more RAM than available.".into(),
                Some(stderr.into()),
            );
        }

        Self::new(
            ErrorCode::MlxProcessError,
            "The MLX model process encountered an unexpected error.".into(),
            Some(stderr.into()),
        )
    }
}

#[derive(Debug, thiserror::Error)]
pub enum ServerError {
    #[error(transparent)]
    Mlx(#[from] MlxError),

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Tauri error: {0}")]
    Tauri(#[from] tauri::Error),
}

impl serde::Serialize for ServerError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let error_to_serialize: MlxError = match self {
            ServerError::Mlx(err) => err.clone(),
            ServerError::Io(e) => MlxError::new(
                ErrorCode::IoError,
                "An input/output error occurred.".into(),
                Some(e.to_string()),
            ),

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Inspect the wrapped `io::Error` — its `kind()` tells you NotFound, PermissionDenied, BrokenPipe, etc.
  2. For NotFound, verify the path the plugin resolved (log it before opening).
  3. For PermissionDenied, check filesystem permissions on the model and cache directories.
  4. For BrokenPipe, handle the case where the MLX process exits mid-write (often a downstream crash — see the MlxProcessError).

Example fix

// before
let f = File::open(&path)?;

// after - map io::Error to a richer MlxError with context
let f = File::open(&path).map_err(|e| match e.kind() {
    io::ErrorKind::NotFound => MlxError::new(ErrorCode::ModelFileNotFound, format!("{} not found", path.display()), None),
    _ => MlxError::new(ErrorCode::IoError, e.to_string(), Some(format!("{}", path.display()))),
})?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn ensure_readable(path: &Path) -> io::Result<()> {
    std::fs::File::open(path).map(|_| ())
}

Type guard

null

Try / catch

match run_mlx(&req).await {
    Ok(out) => Ok(out),
    Err(ServerError::Io(e)) if e.kind() == io::ErrorKind::NotFound => {
        Err(UserError::NotFound(req.model.clone()))
    }
    Err(ServerError::Io(e)) if e.kind() == io::ErrorKind::PermissionDenied => {
        Err(UserError::PermissionDenied(req.model.clone()))
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: Opening a model file that does not exist or is not readable; creating a named pipe / socket to talk to MLX and failing; writing cache or log files to a directory without permission; the subprocess stdin/stdout pipe returning EPIPE.

Common situations: Wrong model path; permission errors on the model directory or cache directory; disk full; broken pipe when the MLX process exits while the plugin is still writing to its stdin.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/11398c2cd9cccd47. Report an issue: GitHub.