{"record":{"id":"d04379d3017b2ebf","repo":"block/buzz","slug":"relay-publish-was-superseded-by-a-session-change-d04379","errorCode":null,"errorMessage":"Relay publish was superseded by a session change.","messagePattern":"Relay publish was superseded by a session change\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/shared/api/relayEventPublisher.ts","lineNumber":64,"sourceCode":"          session.pendingEvents.get(event.id) !== pendingEvent\n        ) {\n          return;\n        }\n\n        // Expected socket recovery must not reject the operation being retried.\n        session.pendingEvents.delete(event.id);\n        const sendError = session.recoverSocketFailure(error, sendErrorMessage);\n        session.pendingEvents.set(event.id, pendingEvent);\n        let retryGeneration: number | null = null;\n\n        try {\n          retryGeneration = await session.reconnect();\n          if (\n            publishOwnership !== session.ownership() ||\n            session.generation() !== retryGeneration ||\n            session.pendingEvents.get(event.id) !== pendingEvent\n          ) {\n            throw new Error(\n              \"Relay publish was superseded by a session change.\",\n            );\n          }\n          await session.send([\"EVENT\", event], retryGeneration);\n        } catch (retryError) {\n          if (session.pendingEvents.get(event.id) !== pendingEvent) return;\n\n          window.clearTimeout(timeout);\n          session.pendingEvents.delete(event.id);\n          reject(\n            publishOwnership === session.ownership() &&\n              retryGeneration !== null &&\n              session.generation() === retryGeneration\n              ? session.recoverSocketFailure(retryError, sendError.message)\n              : session.normalizeError(retryError, sendError.message),\n          );\n        }\n      });","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/shared/api/relayEventPublisher.ts#L46-L82","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Treat as terminal for this attempt: re-create the publish on the current session (new event state) rather than retrying in place.","Catch and check session.ownership()/generation() before any manual re-send.","Avoid triggering multiple reconnects concurrently; route reconnection through the session.","Verify the message wasn't actually delivered before re-publishing to prevent duplicates."],"exampleFix":"// before\nawait publishEvent(event); // retried blindly after failure\n// after\ntry { await publishEvent(event); }\ncatch (e) {\n  if (isSuperseded(e)) return; // publish was superseded; do not double-send\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (session.pendingEvents.get(ev.id) === undefined) return; // already resolved/removed; don't retry\n","typeGuard":"function isSupersededError(e: unknown): boolean { return e instanceof Error && e.message.includes('superseded by a session change'); }\n","tryCatchPattern":"try { await publishEvent(ev); } catch (e) { if (isSupersededError(e)) return; /* do not double-send */ throw e; }\n","preventionTips":["Only one reconnect owner per session","Deduplicate publishes by event id","Check actual delivery before re-publishing"],"tags":["relay","race-condition","retry"],"backgroundTag":"stale-connection-generation","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}