Automattic/harper · warning

userDict path must be a string.

Error message

userDict path must be a string.

What it means

Inside Config::from_lsp_config, the optional `userDictPath` setting must be a JSON string if present. Non-string values abort config parsing. When the string is non-empty it is resolved relative to the workspace root; empty strings are ignored (default dictionary used).

Source

Thrown at harper-ls/src/config.rs:101

impl Config {
    pub fn from_lsp_config(workspace_root: &Path, value: Value) -> Result<Self> {
        let mut base = Config::default();

        let workspace_root = workspace_root.canonicalize()?;
        let workspace_root = workspace_root.as_path();

        let Value::Object(value) = value else {
            bail!("Settings must be an object.");
        };

        let Some(Value::Object(value)) = value.get("harper-ls") else {
            bail!("Settings must contain a \"harper-ls\" key.");
        };

        if let Some(v) = value.get("userDictPath") {
            if !v.is_string() {
                bail!("userDict path must be a string.");
            }

            let path = v.as_str().unwrap();
            if !path.is_empty() {
                base.user_dict_path = path.try_resolve_in(workspace_root)?.to_path_buf();
            }
        }

        if let Some(v) = value.get("fileDictPath") {
            if !v.is_string() {
                bail!("fileDict path must be a string.");
            }

            let path = v.as_str().unwrap();
            if !path.is_empty() {
                base.file_dict_path = path.try_resolve_in(workspace_root)?.to_path_buf();
            }
        }

View on GitHub (pinned to 5fe7d5ab76)

Solutions

  1. Set userDictPath to a string path, e.g. "userDictPath": "./user-dict.txt".
  2. Remove the key to use the default dictionary location.
  3. If generating settings programmatically, ensure paths are converted to strings (and use empty string "" explicitly to mean 'no custom dict').

Example fix

// before
{ "harper-ls": { "userDictPath": ["dict.txt"] } }
// after
{ "harper-ls": { "userDictPath": "dict.txt" } }
Defensive patterns

Strategy: validation

Validate before calling

const p = settings?.['harper-ls']?.userDictPath;
if (p !== undefined && typeof p !== 'string') {
  throw new TypeError('userDictPath must be a string');
}

Type guard

fn is_optional_string(v: &serde_json::Value) -> bool {
    v.get("userDictPath").map_or(true, |s| s.is_string())
}

Try / catch

match Config::from_lsp_config(value, workspace_root) {
    Ok(cfg) => cfg,
    Err(e) => { log::warn!("invalid userDictPath: {e}"); Config::default() }
}

Prevention

When it happens

Trigger: Sending {"harper-ls": {"userDictPath": 42}} or an array/object for userDictPath via LSP settings.

Common situations: Editor settings where the path was written as a list or with wrong type; tooling that programmatically builds settings and passes a Path object that serializes as a non-string; typos in config generators.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Automattic/harper@5fe7d5ab76 (2026-09-06). Data as JSON: /api/errors/1d35fd9b0116a97c. Report an issue: GitHub.