{"record":{"id":"999e025292a28c95","repo":"janhq/jan","slug":"failed-to-save-store","errorCode":null,"errorMessage":"Failed to save store","messagePattern":"Failed to save store","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src-tauri/src/core/setup.rs","lineNumber":75,"sourceCode":"        );\n        if let Err(e) = result {\n            log::error!(\"Failed to add Jan Browser MCP server config: {e}\");\n        }\n    }\n    if mcp_version < 3 {\n        log::info!(\"Migrating MCP schema version 3: Updating Exa to streamable HTTP\");\n        if let Err(e) = migrate_exa_to_http(app_handle.clone()) {\n            log::error!(\"Failed to migrate Exa to HTTP: {e}\");\n        }\n    }\n    if mcp_version < 4 {\n        log::info!(\"Migrating MCP schema version 4: Removing default Exa MCP (native web search cutover)\");\n        if let Err(e) = remove_exa_server(app_handle) {\n            log::error!(\"Failed to remove Exa MCP server: {e}\");\n        }\n    }\n    store.set(\"mcp_version\", 4);\n    store.save().expect(\"Failed to save store\");\n    Ok(())\n}\n\nfn migrate_exa_to_http(app_handle: tauri::AppHandle) -> Result<(), String> {\n    let config_path = get_jan_data_folder_path(app_handle).join(\"mcp_config.json\");\n\n    let config_str =\n        fs::read_to_string(&config_path).map_err(|e| format!(\"Failed to read MCP config: {e}\"))?;\n\n    let mut config: serde_json::Value = serde_json::from_str(&config_str)\n        .map_err(|e| format!(\"Failed to parse MCP config: {e}\"))?;\n\n    if let Some(servers) = config.get_mut(\"mcpServers\").and_then(|s| s.as_object_mut()) {\n        servers.insert(\n            \"exa\".to_string(),\n            serde_json::json!({\n                \"type\": \"http\",\n                \"url\": \"https://mcp.exa.ai/mcp\".to_string(),","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/src-tauri/src/core/setup.rs#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstore.save().expect(\"Failed to save store\");\n\n// after\nif let Err(e) = store.save() {\n    log::error!(\"Failed to save store after MCP migration: {e}\");\n    // continue — migration will retry on next launch\n}","handlingStrategy":"try-catch","validationCode":"// Before saving, verify the store path is writable\nuse std::fs::OpenOptions;\n\nfn verify_store_writable(path: &Path) -> bool {\n    if let Some(parent) = path.parent() {\n        OpenOptions::new().write(true).open(parent).is_ok()\n    } else {\n        false\n    }\n}","typeGuard":null,"tryCatchPattern":"// Replace .expect with a logged error\nif let Err(e) = store.save() {\n    log::error!(\"Failed to save store after MCP migration: {e}. Migration will retry on next launch.\");\n    // Do NOT panic — allow the app to continue; migration is idempotent\n}","preventionTips":["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."],"tags":["panic","store","migration","disk-full","permissions","io"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}