zellij-org/zellij · error

Unspecified key modifier

Error message

Unspecified key modifier

What it means

Thrown by TryFrom<ProtoKeyModifier> for KeyModifier when the proto modifier enum equals Unspecified (its proto3 default, i32 0). The reverse conversion (From<KeyModifier>) never produces it, so encountering it means an inbound key_modifiers entry was never actually set by the sender.

Source

Thrown at zellij-utils/src/ipc/enum_conversions.rs:115

        match modifier {
            KeyModifier::Ctrl => ProtoKeyModifier::Ctrl,
            KeyModifier::Alt => ProtoKeyModifier::Alt,
            KeyModifier::Shift => ProtoKeyModifier::Shift,
            KeyModifier::Super => ProtoKeyModifier::Super,
        }
    }
}

impl TryFrom<ProtoKeyModifier> for KeyModifier {
    type Error = anyhow::Error;

    fn try_from(modifier: ProtoKeyModifier) -> Result<Self> {
        match modifier {
            ProtoKeyModifier::Ctrl => Ok(KeyModifier::Ctrl),
            ProtoKeyModifier::Alt => Ok(KeyModifier::Alt),
            ProtoKeyModifier::Shift => Ok(KeyModifier::Shift),
            ProtoKeyModifier::Super => Ok(KeyModifier::Super),
            ProtoKeyModifier::Unspecified => Err(anyhow!("Unspecified key modifier")),
        }
    }
}

// Helper functions for converting between protobuf i32 and enum types
pub fn bare_key_to_proto_i32(key: BareKey) -> i32 {
    ProtoBareKey::from(key) as i32
}

pub fn bare_key_from_proto_i32(value: i32) -> Result<BareKey> {
    let proto_key = ProtoBareKey::try_from(value)
        .ok()
        .ok_or_else(|| anyhow!("Invalid BareKey value: {}", value))?;
    proto_key.try_into()
}

pub fn key_modifier_to_proto_i32(modifier: KeyModifier) -> i32 {
    ProtoKeyModifier::from(modifier) as i32

View on GitHub (pinned to bf8d23a4f7)

Solutions

  1. Only push real modifier discriminants (Ctrl/Alt/Shift/Super) into key_modifiers; omit the list entirely for no modifiers.
  2. Filter zeros before conversion: retain(|m| *m != 0) on the raw i32 list.
  3. On the zellij side, align client/server versions if well-formed traffic triggers it.

Example fix

// before
let msg = KeyWithModifier { bare_key: BareKey::Enter as i32, key_modifiers: vec![0], character: None };

// after: empty list = no modifiers
let msg = KeyWithModifier { bare_key: BareKey::Enter as i32, key_modifiers: vec![], character: None };
Defensive patterns

Strategy: type-guard

Validate before calling

// strip unset (0) modifier entries before conversion
let mods: Vec<i32> = msg.key_modifiers.into_iter().filter(|m| *m != 0).collect();

Type guard

fn is_valid_modifier(v: i32) -> bool {
    ProtoKeyModifier::from_i32(v)
        .map(|m| m != ProtoKeyModifier::Unspecified)
        .unwrap_or(false)
}

Try / catch

match key_modifier_from_proto_i32(m) {
    Ok(modifier) => Some(modifier),
    Err(e) if e.to_string() == "Unspecified key modifier" => None, // unset entry: skip
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A KeyWithModifier's key_modifiers list contains i32 0 (unset/default) — handcrafted frames, defaulted Vec entries, or an intermediate layer that inserts a zero-initialized modifier — and key_modifier_from_proto_i32 / try_into surfaces the error.

Common situations: Custom client code pushing placeholder modifier values; protobuf round-trips through code that reconstructs repeated fields with Default::default(); tests with empty modifier entries.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of zellij-org/zellij@bf8d23a4f7 (2026-08-19). Data as JSON: /api/errors/51460a45e08d7bdd. Report an issue: GitHub.