wezterm/wezterm · error · anyhow::Error

failed to create feature from {}

Error message

failed to create feature from {}

What it means

feature_from_string wraps HarfBuzz's hb_feature_from_string, which parses OpenType feature selectors like 'kern', '-liga', 'ss01=2' or 'calt=0' into an hb_feature_t. The parser is strict about syntax and returns false (mapped to this Err) for anything that is not tag, optional +/- sign, optional =value or =start:end ranges. wezterm feeds it entries from the user's harfbuzz_features config array, so a malformed entry surfaces here.

Source

Thrown at wezterm-font/src/hbwrap.rs:39

}

pub const IS_PNG: hb_tag_t = hb_tag(b'p', b'n', b'g', b' ');
#[allow(unused)]
pub const IS_SVG: hb_tag_t = hb_tag(b's', b'v', b'g', b' ');
pub const IS_BGRA: hb_tag_t = hb_tag(b'B', b'G', b'R', b'A');

pub fn language_from_string(s: &str) -> Result<hb_language_t, Error> {
    unsafe {
        let lang = hb_language_from_string(s.as_ptr() as *const c_char, s.len() as i32);
        ensure!(!lang.is_null(), "failed to convert {} to language", s);
        Ok(lang)
    }
}

pub fn feature_from_string(s: &str) -> Result<hb_feature_t, Error> {
    unsafe {
        let mut feature = mem::zeroed();
        ensure!(
            hb_feature_from_string(
                s.as_ptr() as *const c_char,
                s.len() as i32,
                &mut feature as *mut _,
            ) != 0,
            "failed to create feature from {}",
            s
        );
        Ok(feature)
    }
}

#[derive(Debug)]
pub struct Blob {
    blob: *mut hb_blob_t,
}

impl Drop for Blob {

View on GitHub (pinned to 3ff7522b96)

Solutions

  1. Rewrite each entry in HarfBuzz syntax: optional leading +/-, a 4-character tag, then optional =number or =start:end — e.g. 'calt=1' not 'calt on'
  2. Remove empty strings and stray whitespace from the harfbuzz_features list in your wezterm config
  3. Test a suspect feature string with `hb-shape --features='YOURSTRING' /path/to/font.ttf` — HarfBuzz's CLI reports the same parse error
  4. If you need boolean wording in config, translate it before calling: 'kern off' -> 'kern=0', 'liga on' -> 'liga=1'

Example fix

-- before (wezterm config)
harfbuzz_features = { "calt off", "liga on" } -- spaces -> hb_feature_from_string fails

-- after
harfbuzz_features = { "calt=0", "liga=1" }
Defensive patterns

Strategy: validation

Validate before calling

fn plausible_feature(s: &str) -> bool {
    let s = s.strip_prefix(['+', '-']).unwrap_or(s);
    let mut parts = s.splitn(2, '=');
    let tag = parts.next().unwrap_or("");
    let tag_ok = tag.len() == 4 && tag.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_');
    match parts.next() {
        None => tag_ok,
        Some(v) => tag_ok && !v.is_empty() && v.bytes().all(|b| b.is_ascii_digit() || b == b':'),
    }
}

Type guard

fn is_valid_hb_feature(s: &str) -> bool { plausible_feature(s) }

Try / catch

for spec in features {
    match feature_from_string(spec) {
        Ok(f) => buf.add_feature(f),
        Err(err) => log::warn!("ignoring bad harfbuzz feature {spec:?}: {err:#}"),
    }
}

Prevention

When it happens

Trigger: Passing strings with wrong separator or value syntax: 'calt on' (space instead of '='), 'liga:' (dangling range), 'kern=maybe' (non-numeric value), '' (empty), or '=2' (missing tag). Correct forms: 'smcp', '-liga', 'ss02=1', 'kern=0'.

Common situations: wezterm config harfbuzz_features written in CSS-ish syntax ('liga off') instead of HarfBuzz syntax ('-liga'); copy-pasted snippets from font-documentation sites that use human wording; trailing whitespace/quotes introduced by scripting the config.

Related errors


AI-assisted analysis of wezterm/wezterm@3ff7522b96 (2026-08-20). Data as JSON: /api/errors/e1f12a19c64c3d3c. Report an issue: GitHub.