zellij-org/zellij · error · anyhow::Error

Empty character string

Error message

Empty character string

What it means

When converting a protobuf KeyWithModifier with bare_key == BareKey::Char, the converter takes the character string and reads its first char with .chars().next(). A Some("") (present but empty string) yields None from chars().next(), so the conversion fails with 'Empty character string'. This is distinct from the missing-field error: the field was sent but carried no character.

Source

Thrown at zellij-utils/src/ipc/protobuf_conversion.rs:3165

{
    type Error = anyhow::Error;
    fn try_from(
        key: crate::client_server_contract::client_server_contract::KeyWithModifier,
    ) -> Result<Self> {
        use crate::ipc::enum_conversions::{bare_key_from_proto_i32, key_modifier_from_proto_i32};
        use std::collections::BTreeSet;

        // Handle character keys specially
        let bare_key = if key.bare_key
            == crate::client_server_contract::client_server_contract::BareKey::Char as i32
        {
            let character_str = key
                .character
                .ok_or_else(|| anyhow!("Character key missing character data"))?;
            let character = character_str
                .chars()
                .next()
                .ok_or_else(|| anyhow!("Empty character string"))?;
            crate::data::BareKey::Char(character)
        } else {
            bare_key_from_proto_i32(key.bare_key)?
        };

        let key_modifiers: Result<BTreeSet<_>> = key
            .key_modifiers
            .into_iter()
            .map(|modifier| key_modifier_from_proto_i32(modifier))
            .collect();

        Ok(Self {
            bare_key,
            key_modifiers: key_modifiers?,
        })
    }
}

View on GitHub (pinned to 98a0837077)

Solutions

  1. Ensure character contains at least one char: Some(c.to_string()) where c: char.
  2. If there is no character, do not send bare_key = Char; use the appropriate named key or drop the event.
  3. Add a unit test over every key event your code emits, asserting character is Some and non-empty.
  4. Validate at the boundary: reject Char keys with empty character strings before they reach the converter.

Example fix

// before
let key = KeyWithModifier {
    bare_key: BareKey::Char as i32,
    key_modifiers: vec![],
    character: Some(String::new()), // -> "Empty character string"
};

// after
let key = KeyWithModifier {
    bare_key: BareKey::Char as i32,
    key_modifiers: vec![],
    character: Some("a".to_string()),
};
Defensive patterns

Strategy: validation

Validate before calling

fn key_is_convertible(k: &KeyWithModifier) -> bool {
    if k.bare_key == BareKey::Char as i32 {
        k.character.as_deref().map(|s| !s.is_empty()).unwrap_or(false)
    } else {
        true
    }
}

Type guard

fn char_key_has_payload(k: &KeyWithModifier) -> bool {
    match (k.bare_key == BareKey::Char as i32, k.character.as_deref()) {
        (true, Some(s)) => s.chars().next().is_some(),
        (true, None) => false,
        (false, _) => true,
    }
}

Try / catch

match zellij_utils::data::Key::try_from(proto_key) {
    Ok(key) => handle_key(key),
    Err(e) if e.to_string().contains("Empty character string") => {
        log::warn!("dropping Char key with empty character string");
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Sending KeyWithModifier { bare_key: Char, character: Some(String::new()) } - e.g. serializing a key event where the character was stripped, lowercased/truncated to nothing, or defaulted to an empty string by a template.

Common situations: Key-forwarding code that does character.map(|c| c.to_string()) on an empty slice; string processing that empties the char (e.g. filtering multi-byte sequences); test fixtures using empty strings as placeholders.

Related errors


AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16). Data as JSON: /api/errors/78b10411f0161f29. Report an issue: GitHub.