janhq/jan · error · ServerError

IO error

Error message

IO error: {0}

What it means

ServerError::Io wraps std::io::Error from command handlers in the plugin, displayed as "IO error: {0}". It is produced automatically via #[from] whenever a server command performs file or process I/O that fails. It lets the ServerError enum cover filesystem and stdio failures without a bespoke variant.

Solutions

  1. Inspect the wrapped std::io::Error and its kind; check NotFound, PermissionDenied, etc.
  2. Verify the configured model/binary paths exist and are readable/executable before invoking the command.
  3. Check disk space and file permissions in the plugin's data directories.
  4. If path-dependent, resolve with an absolute path and validate it on the frontend first.

Example fix

// before: passing a relative, unverified model path
let path = "models/foo.gguf";
// after: validate before calling the command
let path = std::path::absolute("models/foo.gguf")?;
if !path.exists() { return Err(ServerError::InvalidArgument(format!("missing model {}", path.display()))); }
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_readable(path: &std::path::Path) -> Result<(), ServerError> {
    let meta = std::fs::metadata(path).map_err(ServerError::Io)?;
    if meta.is_file() { Ok(()) } else { Err(ServerError::InvalidArgument(format!("{} is not a file", path.display()))) }
}

Try / catch

match cmd() {
    Err(ServerError::Io(e)) => match e.kind() {
        std::io::ErrorKind::NotFound => show("path not found"),
        std::io::ErrorKind::PermissionDenied => show("check permissions"),
        _ => show(&e.to_string()),
    },
    other => other,
}

Prevention

When it happens

Trigger: A #[tauri::command] returning ServerError performs std::io operations (reading model files, spawning the llama server process, writing temp files) and the underlying call returns Err(std::io::Error), converted by the #[from] impl.

Common situations: Model file missing or unreadable at the configured path, disk full when writing caches, permission denied on the llama-server binary, or process spawn failures (executable not found).

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/4ba5def18fb95e72. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/plugins/tauri-plugin-llamacpp/src/error.rs:199

fn basename(path: &str) -> &str {
    path.rsplit(['/', '\\']).next().unwrap_or(path)
}

fn push_library(found: &mut Vec<String>, candidate: &str) {
    let candidate = candidate.trim_matches(['\'', '"', '(', ')', ',', '.'].as_ref());
    if looks_like_library(candidate) && !found.iter().any(|f| f == candidate) {
        found.push(candidate.to_string());
    }
}

// Error type for server commands
#[derive(Debug, thiserror::Error)]
pub enum ServerError {
    #[error(transparent)]
    Llamacpp(#[from] LlamacppError),

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

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

    #[error("Invalid argument: {0}")]
    InvalidArgument(String),
}

// impl serialization for tauri
impl serde::Serialize for ServerError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let error_to_serialize: LlamacppError = match self {
            ServerError::Llamacpp(err) => err.clone(),
            ServerError::Io(e) => LlamacppError::new(

View on GitHub (pinned to 7205d770c1)