{"record":{"id":"ca4a3df37b5801d9","repo":"libnyanpasu/clash-nyanpasu","slug":"failed-to-open-storage-e","errorCode":null,"errorMessage":"failed to open storage: {e}","messagePattern":"failed to open storage: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/core/migration/modules/storage.rs","lineNumber":100,"sourceCode":"        let Some(hotkeys_value) = config.get(&hotkeys_key).cloned() else {\n            return Ok(());\n        };\n        let Some(hotkeys) = hotkeys_value.as_sequence() else {\n            return Ok(());\n        };\n\n        let hotkey_strings: Vec<String> = hotkeys\n            .iter()\n            .filter_map(|value| value.as_str().map(ToString::to_string))\n            .collect();\n\n        if !hotkey_strings.is_empty() {\n            let storage_path = ctx.storage_path();\n            if let Some(parent) = storage_path.parent() {\n                std::fs::create_dir_all(parent)?;\n            }\n            let storage = Storage::try_new(&storage_path)\n                .map_err(|e| anyhow::anyhow!(\"failed to open storage: {e}\"))?;\n\n            storage\n                .set_item(\"hotkeys\", &hotkey_strings)\n                .map_err(|e| anyhow::anyhow!(\"failed to save hotkeys: {e}\"))?;\n\n            // Note: registration is intentionally NOT done here. This migration\n            // runs in a separate `migrate` subprocess with no Tauri app handle, so\n            // `Hotkey::update` would fail; `Hotkey::init` reads the migrated value\n            // from KV storage at app startup instead.\n            tracing::info!(\"migrated {} hotkeys to KV storage\", hotkey_strings.len());\n        }\n\n        config.remove(&hotkeys_key);\n        let new_config = serde_yaml::to_string(&config)\n            .map_err(|e| anyhow::anyhow!(\"failed to serialize config: {e}\"))?;\n        crate::core::migration::fs::atomic_write(&config_path, new_config.as_bytes())?;\n\n        Ok(())","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/migration/modules/storage.rs#L82-L118","documentation":"After extracting hotkey strings from config.yaml, the migration opens the KV storage database via Storage::try_new(storage_path) to write the migrated values. If the storage backend cannot be created or opened (I/O error, corrupt database file, locked file, bad path), this error aborts the migration before any hotkeys are written.","triggerScenarios":"Storage::try_new fails because the storage directory cannot be used: parent dir creation succeeded but the DB file is corrupt, locked by another process (a running app instance holds it), permissions deny access, or the path is invalid/on a full disk.","commonSituations":"Nyanpasu still running and holding the storage lock during migration; corrupted KV database from a previous crash; read-only install directory or antivirus blocking file creation; network/roaming profile paths unavailable.","solutions":["Close all running Nyanpasu instances (and the migrate subprocess) so the storage file is not locked, then retry.","Check {e} for the underlying cause: fix directory permissions or free disk space at the storage path.","If the KV database file is corrupt, back it up and remove/rename it so a fresh storage is created (other stored settings may need re-entry).","Exclude the app data directory from antivirus/controlled-folder-access interference and verify the path is writable.","Re-run the migration after remediation; config.yaml was not modified on this failure path."],"exampleFix":"// before\nlet storage = Storage::try_new(&storage_path)\n    .map_err(|e| anyhow::anyhow!(\"failed to open storage: {e}\"))?;\n// after\nlet storage = Storage::try_new(&storage_path)\n    .with_context(|| format!(\"failed to open storage at {} — close running instances and check permissions\", storage_path.display()))?;","handlingStrategy":"try-catch","validationCode":"fn storage_path_writable(p: &Path) -> Result<(), String> {\n    if let Some(parent) = p.parent() {\n        std::fs::create_dir_all(parent).map_err(|e| format!(\"mkdir: {e}\"))?;\n    }\n    let probe = p.with_extension(\"write-probe\");\n    std::fs::write(&probe, b\"\").map_err(|e| format!(\"not writable: {e}\"))?;\n    std::fs::remove_file(&probe).map_err(|e| format!(\"cleanup: {e}\"))?;\n    Ok(())\n} // also ensure no other app instance is running and holds the DB lock","typeGuard":"null","tryCatchPattern":"let storage = match Storage::try_new(&storage_path) {\n    Ok(s) => s,\n    Err(e) => {\n        eprintln!(\"close running Nyanpasu instances and check permissions at {}: {e}\", storage_path.display());\n        return Err(anyhow::anyhow!(\"failed to open storage: {e}\"));\n    }\n};","preventionTips":["Ensure the app and migrate subprocess are not running concurrently with the migration","Verify the app-data directory is writable and excluded from controlled-folder access/AV","Back up the KV storage file before migrating; replace it if corrupt","Check free disk space on the volume before running migrations"],"tags":["storage","kv-database","migration","hotkeys","io"],"backgroundTag":"file-open-failed","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}