Skip to content

Prose delivery and typing take the first server's channel for a bare channel id two servers share; the stream and channel tools refuse it #474

Open

Description

@Anarchid

What's wrong

In src/mcpl/channel-registry.ts (main e670363), code paths disagree on what to do when two MCPL servers register the same channel id. MCPL §14.2 requires ids to be unique only within one connection, so this is legal.

Path Function Behaviour
Prose delivery (routeSpeech) findChannelEntry (~2345–2352) Takes the first match, whichever server it belongs to
Typing indicators (~1269, ~1278, ~1314) Take the first match
Lookup at ~3240 Takes the first match
Outgoing stream streamingServerFor (~3072–3078) Returns null on the ambiguity
Channel tools resolveToolChannelEntry Refuse: "Channel id is ambiguous … Include serverId"

Why it matters

An agent attached to two servers with the same id convention (two Discord connectors, or two Zulip realms with shared stream names) can see:

  • its bare prose posted to the wrong server, with no error;
  • its typing indicator shown in the wrong place;
  • the stream for the channel actually published to dropped.

Which server wins depends on registration order, so a reconnect can change it.

How it was found

During the review of #360, which moves the outgoing stream to follow successful publishes. Confirmed on main. No existing issue was found.

Suggested fix

  • Resolve the locus once, using the same ambiguity rule as the channel tools. Fail delivery loudly (mcpl:speech-route-failed) when the id is ambiguous, or carry the server id with the locus.
  • Return the publishing entry's serverId from routeSpeech so the stream follows that exact server.
  • Add a two-server duplicate-id test covering delivery, typing and streaming.

Found during our review of #360: #360 (review)

Posted by the maintainer's desk assistant.

Activity

  1. nissa-seru commented on Oct 21, 2026

    @nissa-seru
    Contributor

    One more place that reads a channel by its id alone: the invitation an agent gets when a message addressed to it arrives in a closed channel. buildClosedChannelInvitation is given the message's server, but asks the registry about the channel without it, through isChannelOpen(opts.channelId) and getDescriptor(opts.channelId) (src/framework.ts:12824-12825, as of main at 97ca7eba). Both go through findChannelEntry. So for an id two servers share, whether the agent is invited at all comes from the first server's entry, and so does the label it's shown on the message path (src/framework.ts:12215), which passes no label of its own. That's as with prose delivery and typing above.

    We found this by reading, in an internal review of #598, and haven't run it.

    Written by Petra-1012.

    — Lottie-2661

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions