Kuberwastaken/claurst · error · anyhow::Error

Failed to parse settings file

Error message

Failed to parse settings file {}: {}. The file was not modified; fix the JSON and restart Claurst.

What it means

Claurst could not deserialize the user's settings.json file with serde_json. Because the file cannot be parsed, Claurst refuses to proceed (and never auto-modifies the file) so the user does not lose their configuration. The error message embeds the file path and the serde_json detail (e.g. 'expected value at line 3 column 5') to pinpoint the syntax problem.

Solutions

  1. Open the file printed in the error, fix the JSON at the line/column given in the serde detail, and restart Claurst — Claurst never modifies the file itself.
  2. Validate the file with `jq . settings.json` (or any JSON linter) to find the exact syntax problem before restarting.
  3. If the expected value shape changed (e.g. an enum string), check current docs for valid values for that field.
  4. As a last resort, back up settings.json, remove/rename it so Claurst regenerates defaults, and re-apply settings incrementally.

Example fix

// before (invalid JSON: trailing comma)
{
  "theme": "dark",
}

// after
{
  "theme": "dark"
}
Defensive patterns

Strategy: validation

Validate before calling

// Rust
fn settings_json_valid(path: &std::path::Path) -> bool {
    std::fs::read_to_string(path)
        .map(|c| serde_json::from_str::<serde_json::Value>(&c).is_ok())
        .unwrap_or(false)
}
// call before starting Claurst / relying on settings:
// if !settings_json_valid(&Settings::global_settings_path()) { /* repair first */ }

Type guard

fn is_valid_settings(path: &Path) -> bool {
    std::fs::read_to_string(path)
        .map(|c| serde_json::from_str::<serde_json::Value>(&c).is_ok())
        .unwrap_or(false)
}

Try / catch

match Settings::load() {
    Ok(s) => s,
    Err(e) => { eprintln!("{e:#}"); eprintln!("Fix settings.json manually, then restart."); std::process::exit(1); }
}

Prevention

When it happens

Trigger: Settings load calls `Settings::parse_file(content, path)` on the contents of the global settings.json (`Settings::global_settings_path()` = config_dir().join("settings.json")). Any invalid JSON — trailing commas, single quotes instead of double quotes, comments, unquoted keys, truncated file, or a valid-JSON value with the wrong shape (e.g. a string where an object/enum is expected) — triggers this.

Common situations: Hand-editing settings.json and leaving a trailing comma; an editor or script writing JSONC-style comments; a crashed write leaving a truncated file; a settings field renamed across versions so the old value no longer matches the expected enum/shape.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/208fe6afc3f989af. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/core/src/lib.rs:1715

            // 3. XDG config location for fresh installs.
            if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") {
                let xdg = PathBuf::from(xdg);
                // Per the XDG spec a relative $XDG_CONFIG_HOME must be ignored.
                if xdg.is_absolute() {
                    return xdg.join("claurst");
                }
            }
            home.join(".config").join("claurst")
        }

        /// Full path to the global settings JSON file.
        pub fn global_settings_path() -> PathBuf {
            Self::config_dir().join("settings.json")
        }

        fn parse_file(content: &str, path: &Path) -> anyhow::Result<Self> {
            serde_json::from_str(content).map_err(|error| {
                anyhow::anyhow!(
                    "Failed to parse settings file {}: {}. The file was not modified; fix the JSON and restart Claurst.",
                    path.display(),
                    error
                )
            })
        }

        async fn load_from_path(path: &Path) -> anyhow::Result<Self> {
            if path.exists() {
                let content = tokio::fs::read_to_string(path).await?;
                Self::parse_file(&content, path)
            } else {
                Ok(Self::default())
            }
        }

        fn load_from_path_sync(path: &Path) -> anyhow::Result<Self> {
            if path.exists() {

View on GitHub (pinned to b0637c97ec)