block/buzz · error · Error

Relay disconnected for community switch.

Error message

Relay disconnected for community switch.

What it means

publishSessionEvent snapshots the session's publish ownership before the rate-limit wait and throws if ownership changed — the relay session was disconnected/replaced for a community switch while this publish was pending. It guarantees a publish never proceeds under a session that no longer owns the connection.

Source

Thrown at desktop/src/shared/api/relayEventPublisher.ts:26

  ownership: () => number;
  pendingEvents: Map<string, PendingEvent>;
  send: (payload: unknown[], generation: number) => Promise<void>;
  reconnect: () => Promise<number>;
  normalizeError: (error: unknown, fallback: string) => Error;
  recoverSocketFailure: (error: unknown, fallback: string) => Error;
};

/** Publish once, with one reconnect retry, without crossing session ownership. */
export async function publishSessionEvent(
  session: PublishSession,
  event: RelayEvent,
  timeoutMessage: string,
  sendErrorMessage: string,
): Promise<RelayEvent> {
  const publishOwnership = session.ownership();
  await waitForRateLimit();
  if (publishOwnership !== session.ownership()) {
    throw new Error("Relay disconnected for community switch.");
  }
  const publishGeneration = session.generation();

  return new Promise<RelayEvent>((resolve, reject) => {
    const timeout = window.setTimeout(() => {
      session.pendingEvents.delete(event.id);
      reject(new Error(timeoutMessage));
    }, PUBLISH_TIMEOUT_MS);
    const pendingEvent = { event, resolve, reject, timeout };
    session.pendingEvents.set(event.id, pendingEvent);

    void session
      .send(["EVENT", event], publishGeneration)
      .catch(async (error) => {
        // A disconnect may already have rejected this operation while the send
        // was in flight. Its late failure must not reset the replacement session.
        if (
          publishOwnership !== session.ownership() ||

View on GitHub (pinned to dad5a33865)

Solutions

  1. Delay or cancel publishes during a community switch and re-dispatch on the new session.
  2. Catch and retry after the switch completes (fresh ownership/generation).
  3. Gate send buttons on community-switch-in-progress state.
  4. Check for multiple components publishing through a session being torn down.

Example fix

// before
await publishSessionEvent(session, event, ...);
// after
try { await publishSessionEvent(session, event, ...); }
catch (e) {
  if (isSuperseded(e)) await publishSessionEvent(session, event, ...); // retry on new session
  else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (session.switchInProgress()) await session.switchComplete; // hold publishes until stable

Type guard

function isSupersededError(e: unknown): boolean { return e instanceof Error && e.message.includes('superseded by a session change'); }

Try / catch

try { await publishEvent(ev); } catch (e) { if (isSupersededError(e) || e.message.includes('community switch')) await republishOnCurrentSession(ev); else throw e; }

Prevention

When it happens

Trigger: Calling publishEvent() (via publishSessionEvent) while a community switch is disconnecting/replacing the session; waitForRateLimit() yields and, on resume, the ownership token no longer matches.

Common situations: User switches communities while a message/media event is publishing; logout during publish; rapid community switching with queued events.

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-09-05). Data as JSON: /api/errors/48ba00db81a671c5. Report an issue: GitHub.