janhq/jan · critical

Failed to save store

Error message

Failed to save store

What it means

This is a panic (.expect) from store.save() during MCP schema migration in the app setup phase. The store is backed by the Tauri plugin-store, which writes JSON to a file on disk. save() fails when the target file is not writable (permissions), the disk is full, the path is invalid, or an I/O error occurs mid-write.

Source

Thrown at src-tauri/src/core/setup.rs:75

        );
        if let Err(e) = result {
            log::error!("Failed to add Jan Browser MCP server config: {e}");
        }
    }
    if mcp_version < 3 {
        log::info!("Migrating MCP schema version 3: Updating Exa to streamable HTTP");
        if let Err(e) = migrate_exa_to_http(app_handle.clone()) {
            log::error!("Failed to migrate Exa to HTTP: {e}");
        }
    }
    if mcp_version < 4 {
        log::info!("Migrating MCP schema version 4: Removing default Exa MCP (native web search cutover)");
        if let Err(e) = remove_exa_server(app_handle) {
            log::error!("Failed to remove Exa MCP server: {e}");
        }
    }
    store.set("mcp_version", 4);
    store.save().expect("Failed to save store");
    Ok(())
}

fn migrate_exa_to_http(app_handle: tauri::AppHandle) -> Result<(), String> {
    let config_path = get_jan_data_folder_path(app_handle).join("mcp_config.json");

    let config_str =
        fs::read_to_string(&config_path).map_err(|e| format!("Failed to read MCP config: {e}"))?;

    let mut config: serde_json::Value = serde_json::from_str(&config_str)
        .map_err(|e| format!("Failed to parse MCP config: {e}"))?;

    if let Some(servers) = config.get_mut("mcpServers").and_then(|s| s.as_object_mut()) {
        servers.insert(
            "exa".to_string(),
            serde_json::json!({
                "type": "http",
                "url": "https://mcp.exa.ai/mcp".to_string(),

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Verify the app data directory is writable: `ls -la` on the store file location.
  2. Free disk space if the volume is full.
  3. Check file permissions and ownership of the data directory.
  4. Replace .expect with a logged error so migration failure does not crash startup.

Example fix

// before
store.save().expect("Failed to save store");

// after
if let Err(e) = store.save() {
    log::error!("Failed to save store after MCP migration: {e}");
    // continue — migration will retry on next launch
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before saving, verify the store path is writable
use std::fs::OpenOptions;

fn verify_store_writable(path: &Path) -> bool {
    if let Some(parent) = path.parent() {
        OpenOptions::new().write(true).open(parent).is_ok()
    } else {
        false
    }
}

Try / catch

// Replace .expect with a logged error
if let Err(e) = store.save() {
    log::error!("Failed to save store after MCP migration: {e}. Migration will retry on next launch.");
    // Do NOT panic — allow the app to continue; migration is idempotent
}

Prevention

When it happens

Trigger: The app data directory is read-only or the store file is locked by another process. Disk full during the write. Parent directory was deleted between open and write. Filesystem corruption or remounted read-only. Migration code runs but the store path resolved to a location without write permission.

Common situations: Running the app from a read-only AppImage mount without proper overlay. Disk full on a small VM. Permission mismatch after a user/group change. Antivirus or file locking on Windows preventing the write. Snap/Flatpak sandbox denying write to the data path.

Related errors


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