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
- Ensure character contains at least one char: Some(c.to_string()) where c: char.
- If there is no character, do not send bare_key = Char; use the appropriate named key or drop the event.
- Add a unit test over every key event your code emits, asserting character is Some and non-empty.
- 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
- Only the first char of the string is used - never send multi-char or empty strings by design.
- Build the field from a Rust char: Some(c.to_string()) so it is never empty.
- Fuzz key serialization with edge cases: empty string, multi-byte chars, combining marks.
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
- Character key missing character data
- PageScrollUpByPaneId missing pane_id
- PageScrollDownByPaneId missing pane_id
- HalfPageScrollUpByPaneId missing pane_id
- HalfPageScrollDownByPaneId missing pane_id
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/78b10411f0161f29.
Report an issue: GitHub.