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
- Verify the app data directory is writable: `ls -la` on the store file location.
- Free disk space if the volume is full.
- Check file permissions and ownership of the data directory.
- 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
- Replace .expect with logged error return — migration is idempotent and retries on next launch.
- Verify the data directory is writable at startup.
- Monitor disk space in long-running deployments.
- Ensure file permissions are correct after user/group changes or updates.
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.