libnyanpasu/clash-nyanpasu · error

failed to parse config: {e}

Error message

failed to parse config: {e}

What it means

detect_baseline reads the hotkey config.yaml to decide which migration baseline the installation is at. It parses the file into a serde_yaml Mapping; if parsing fails, this error is returned instead of a revision number, so the migration framework cannot determine the starting point.

Source

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

const HOTKEYS_KEY: &str = "hotkeys";

pub struct StorageMigrator;

impl ModuleMigrator for StorageMigrator {
    fn module(&self) -> &'static str {
        "storage"
    }

    fn detect_baseline(&self, ctx: &Ctx) -> anyhow::Result<u64> {
        let config_path = ctx.nyanpasu_config_path();
        if !config_path.exists() {
            return Ok(current_revision());
        }

        let raw = std::fs::read_to_string(&config_path)?;
        let config: Mapping = serde_yaml::from_str(&raw)
            .map_err(|e| anyhow::anyhow!("failed to parse config: {e}"))?;
        if config
            .get(HOTKEYS_KEY)
            .is_some_and(|value| value.as_sequence().is_some())
        {
            Ok(0)
        } else {
            Ok(current_revision())
        }
    }

    fn steps(&self) -> &'static [&'static dyn MigrationStep] {
        &STEPS
    }
}

#[derive(Debug, Clone, Copy)]
pub struct MigrateHotkeysToKv;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Fix the YAML syntax error at the line/column reported in {e}.
  2. Restore config.yaml from backup or let the app regenerate defaults by moving the corrupt file aside.
  3. Validate externally with a YAML parser before re-running migration.
  4. Ensure the file is UTF-8 without BOM, spaces-only indentation.

Example fix

// before
let config: Mapping = serde_yaml::from_str(&raw)
    .map_err(|e| anyhow::anyhow!("failed to parse config: {e}"))?;
// after
let config: Mapping = serde_yaml::from_str(&raw)
    .with_context(|| format!("failed to parse config at {} for baseline detection", config_path.display()))?;
Defensive patterns

Strategy: validation

Validate before calling

fn config_yaml_ok(path: &Path) -> Result<(), String> {
    let raw = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
    serde_yaml::from_str::<serde_yaml::Mapping>(&raw)
        .map(|_| ())
        .map_err(|e| format!("{}: {e}", path.display()))
}

Type guard

fn is_valid_config_yaml(raw: &str) -> bool {
    serde_yaml::from_str::<serde_yaml::Mapping>(raw).is_ok()
}

Try / catch

let config: Mapping = match serde_yaml::from_str(&raw) {
    Ok(c) => c,
    Err(e) => {
        eprintln!("cannot detect baseline, config.yaml invalid: {e}");
        eprintln!("fix or restore config.yaml, then re-run migration");
        return Err(anyhow::anyhow!("failed to parse config: {e}"));
    }
};

Prevention

When it happens

Trigger: config.yaml exists but serde_yaml::from_str::<Mapping> fails: invalid YAML syntax, top-level non-mapping document, duplicate keys, BOM/non-UTF8 bytes, or truncation from a previous bad write.

Common situations: User hand-edited config.yaml and broke syntax; corrupt file from an old version or crash; encoding issues from external editors.

Understand the failure class

Related errors


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