block/buzz · error · Error

Relay publish was superseded by a session change.

Error message

Relay publish was superseded by a session change.

What it means

After a failed send, publishSessionEvent attempts one reconnect and re-send. Before re-sending it re-validates three fences: ownership is unchanged, the session generation equals the generation returned by session.reconnect(), and this event is still the pending one. Any mismatch means the publish was superseded by a newer session or the event was removed from pending, so it throws instead of double-sending.

Source

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

          session.pendingEvents.get(event.id) !== pendingEvent
        ) {
          return;
        }

        // Expected socket recovery must not reject the operation being retried.
        session.pendingEvents.delete(event.id);
        const sendError = session.recoverSocketFailure(error, sendErrorMessage);
        session.pendingEvents.set(event.id, pendingEvent);
        let retryGeneration: number | null = null;

        try {
          retryGeneration = await session.reconnect();
          if (
            publishOwnership !== session.ownership() ||
            session.generation() !== retryGeneration ||
            session.pendingEvents.get(event.id) !== pendingEvent
          ) {
            throw new Error(
              "Relay publish was superseded by a session change.",
            );
          }
          await session.send(["EVENT", event], retryGeneration);
        } catch (retryError) {
          if (session.pendingEvents.get(event.id) !== pendingEvent) return;

          window.clearTimeout(timeout);
          session.pendingEvents.delete(event.id);
          reject(
            publishOwnership === session.ownership() &&
              retryGeneration !== null &&
              session.generation() === retryGeneration
              ? session.recoverSocketFailure(retryError, sendError.message)
              : session.normalizeError(retryError, sendError.message),
          );
        }
      });

View on GitHub (pinned to dad5a33865)

Solutions

  1. Treat as terminal for this attempt: re-create the publish on the current session (new event state) rather than retrying in place.
  2. Catch and check session.ownership()/generation() before any manual re-send.
  3. Avoid triggering multiple reconnects concurrently; route reconnection through the session.
  4. Verify the message wasn't actually delivered before re-publishing to prevent duplicates.

Example fix

// before
await publishEvent(event); // retried blindly after failure
// after
try { await publishEvent(event); }
catch (e) {
  if (isSuperseded(e)) return; // publish was superseded; do not double-send
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (session.pendingEvents.get(ev.id) === undefined) return; // already resolved/removed; don't retry

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)) return; /* do not double-send */ throw e; }

Prevention

When it happens

Trigger: Retry path raced a second community switch or reconnect (ownership or generation changed), or pendingEvents no longer contains this event (it was cleared/committed elsewhere) between reconnect and re-send.

Common situations: Rapid community switches during a flaky connection; duplicate reconnects triggered concurrently; event already removed from pendingEvents by a timeout or teardown while the retry was in flight.

Related errors


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