libnyanpasu/clash-nyanpasu · error

failed to open storage

Error message

failed to open storage: {e}

What it means

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.

Solutions

  1. Close all running Nyanpasu instances (and the migrate subprocess) so the storage file is not locked, then retry.
  2. Check {e} for the underlying cause: fix directory permissions or free disk space at the storage path.
  3. 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).
  4. Exclude the app data directory from antivirus/controlled-folder-access interference and verify the path is writable.
  5. Re-run the migration after remediation; config.yaml was not modified on this failure path.

Example fix

// before
let storage = Storage::try_new(&storage_path)
    .map_err(|e| anyhow::anyhow!("failed to open storage: {e}"))?;
// after
let storage = Storage::try_new(&storage_path)
    .with_context(|| format!("failed to open storage at {} — close running instances and check permissions", storage_path.display()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn storage_path_writable(p: &Path) -> Result<(), String> {
    if let Some(parent) = p.parent() {
        std::fs::create_dir_all(parent).map_err(|e| format!("mkdir: {e}"))?;
    }
    let probe = p.with_extension("write-probe");
    std::fs::write(&probe, b"").map_err(|e| format!("not writable: {e}"))?;
    std::fs::remove_file(&probe).map_err(|e| format!("cleanup: {e}"))?;
    Ok(())
} // also ensure no other app instance is running and holds the DB lock

Type guard

null

Try / catch

let storage = match Storage::try_new(&storage_path) {
    Ok(s) => s,
    Err(e) => {
        eprintln!("close running Nyanpasu instances and check permissions at {}: {e}", storage_path.display());
        return Err(anyhow::anyhow!("failed to open storage: {e}"));
    }
};

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/ca4a3df37b5801d9. Report an issue: GitHub.

Appendix: source

Thrown at backend/tauri/src/core/migration/modules/storage.rs:100

        let Some(hotkeys_value) = config.get(&hotkeys_key).cloned() else {
            return Ok(());
        };
        let Some(hotkeys) = hotkeys_value.as_sequence() else {
            return Ok(());
        };

        let hotkey_strings: Vec<String> = hotkeys
            .iter()
            .filter_map(|value| value.as_str().map(ToString::to_string))
            .collect();

        if !hotkey_strings.is_empty() {
            let storage_path = ctx.storage_path();
            if let Some(parent) = storage_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            let storage = Storage::try_new(&storage_path)
                .map_err(|e| anyhow::anyhow!("failed to open storage: {e}"))?;

            storage
                .set_item("hotkeys", &hotkey_strings)
                .map_err(|e| anyhow::anyhow!("failed to save hotkeys: {e}"))?;

            // Note: registration is intentionally NOT done here. This migration
            // runs in a separate `migrate` subprocess with no Tauri app handle, so
            // `Hotkey::update` would fail; `Hotkey::init` reads the migrated value
            // from KV storage at app startup instead.
            tracing::info!("migrated {} hotkeys to KV storage", hotkey_strings.len());
        }

        config.remove(&hotkeys_key);
        let new_config = serde_yaml::to_string(&config)
            .map_err(|e| anyhow::anyhow!("failed to serialize config: {e}"))?;
        crate::core::migration::fs::atomic_write(&config_path, new_config.as_bytes())?;

        Ok(())

View on GitHub (pinned to f7dbce2997)