Automattic/harper · warning

Settings must contain a "harper-ls" key.

Error message

Settings must contain a "harper-ls" key.

What it means

Config::from_lsp_config found the top-level settings object but it lacks an object-valued "harper-ls" key. All Harper language-server options live under that namespace, so it is mandatory in the settings payload.

Source

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

    /// Maximum length (in bytes) a file can have before it's skipped.
    /// Above this limit, the file will not be linted.
    pub max_file_length: usize,
    pub exclude_patterns: GlobSet,
}

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.");
            }

View on GitHub (pinned to 5fe7d5ab76)

Solutions

  1. Nest all options under the "harper-ls" key: settings = { "harper-ls": { ... } }.
  2. Verify your editor config uses the exact server name "harper-ls" (case-sensitive).
  3. Send an empty object ({"harper-ls": {}}) if you only want defaults.

Example fix

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

Strategy: validation

Validate before calling

if (!settings || typeof settings['harper-ls'] !== 'object' || settings['harper-ls'] === null) {
  throw new Error('settings must contain a "harper-ls" object');
}

Type guard

fn has_harper_ls_section(v: &serde_json::Value) -> bool {
    v.get("harper-ls").map_or(false, |s| s.is_object())
}

Try / catch

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

Prevention

When it happens

Trigger: Sending settings like {"otherServer": {...}} or {"harper-ls": "enabled"} (non-object) to harper-ls via initialize options or didChangeConfiguration.

Common situations: Configuring harper-ls under the wrong server name in the editor's LSP settings; nesting harper-ls options at the top level instead of under "harper-ls"; sending harper-ls settings as a string or list.

Related errors


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