zed-industries/zed · error

registered handler for the same message twice

Error message

registered handler for the same message twice

What it means

A ProtoClient is Zed's connection for the collab/rpc protocol; it routes each incoming message type to exactly one handler stored in a map keyed by the message's TypeId. add_message_handler (crates/rpc/src/proto_client.rs:127) installs that mapping and panics if the type already has one, because two handlers would both try to consume the same envelope. This is a wiring assertion: each message type must be registered once per connection.

Source

Thrown at crates/rpc/src/proto_client.rs:127

impl ProtoMessageHandlerSet {
    pub fn clear(&mut self) {
        self.message_handlers.clear();
        self.entities_by_message_type.clear();
        self.entities_by_type_and_remote_id.clear();
        self.entity_id_extractors.clear();
    }

    fn add_message_handler(
        &mut self,
        message_type_id: TypeId,
        entity: gpui::AnyWeakEntity,
        handler: ProtoMessageHandler,
    ) {
        self.entities_by_message_type
            .insert(message_type_id, entity);
        let prev_handler = self.message_handlers.insert(message_type_id, handler);
        if prev_handler.is_some() {
            panic!("registered handler for the same message twice");
        }
    }

    fn add_entity_message_handler(
        &mut self,
        message_type_id: TypeId,
        entity_type_id: TypeId,
        entity_id_extractor: fn(&dyn AnyTypedEnvelope) -> u64,
        handler: ProtoMessageHandler,
    ) {
        self.entity_id_extractors
            .entry(message_type_id)
            .or_insert(entity_id_extractor);
        self.entity_types_by_message_type
            .insert(message_type_id, entity_type_id);
        let prev_handler = self.message_handlers.insert(message_type_id, handler);
        if prev_handler.is_some() {
            panic!("registered handler for the same message twice");

View on GitHub (pinned to 9d272b0363)

Solutions

  1. Move registration so it runs exactly once per connection: install handlers in the function that constructs and owns the ProtoClient.
  2. For reconnect flows, construct a new ProtoClient for the new connection instead of re-registering on the old one.
  3. Use the backtrace to identify the message type and the two call sites that both registered it, then keep one.
  4. In tests, build a fresh client (or client pair) per test rather than reusing a global client.

Example fix

// before: registration runs per session, panics on the second
fn join_room(client: &Arc<TypedProtoClient>) {
    client.add_message_handler(handle_room_message);
}

// after: register once at connection construction
fn build_client(conn: Connection) -> Arc<TypedProtoClient> {
    let client = Arc::new(TypedProtoClient::new(Arc::new(conn)));
    client.add_message_handler(handle_room_message);
    client
}
Defensive patterns

Strategy: validation

Validate before calling

use std::any::TypeId;
use std::collections::HashSet;

struct HandlerRegistry {
    registered: HashSet<TypeId>,
}

impl HandlerRegistry {
    // call before every add_message_handler in shared setup code
    fn can_register(&mut self, id: TypeId) -> bool {
        self.registered.insert(id)
    }
}

Prevention

When it happens

Trigger: Calling client.add_message_handler (or typed wrappers such as the request/stream handler helpers on TypedProtoClient) twice for the same message type M on the same connection — typically a setup routine that should run once per connection being invoked again, e.g. a second session join or a re-init path reusing the live client.

Common situations: Opening a second collaborative session over an existing connection; refactors that moved handler registration from connection construction into per-view or per-session code; tests that loop and register handlers on a shared client instead of building a fresh client per iteration.

Related errors


AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-08-20). Data as JSON: /api/errors/4e2b4fdad7c830b9. Report an issue: GitHub.