{"record":{"id":"8b946c20bf515fdd","repo":"block/buzz","slug":"read-only-relay-socket-is-not-connected","errorCode":null,"errorMessage":"Read-only relay socket is not connected.","messagePattern":"Read-only relay socket is not connected\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/shared/api/readOnlyRelayClient.ts","lineNumber":118,"sourceCode":"      this.publishes.delete(eventId);\n    }\n\n    this.onMessageChannel = null;\n    this.connectPromise = null;\n  }\n\n  async fetchEvents(filter: RelaySubscriptionFilter): Promise<RelayEvent[]> {\n    await this.connect();\n    return this.requestHistory(filter);\n  }\n\n  async publishEvent(event: RelayEvent): Promise<void> {\n    await this.connect();\n    const generation = this.generation;\n    await waitForRateLimit();\n\n    if (generation !== this.generation || this.wsId === null) {\n      throw new Error(\"Read-only relay socket is not connected.\");\n    }\n\n    return new Promise<void>((resolve, reject) => {\n      const timeout = window.setTimeout(() => {\n        this.publishes.delete(event.id);\n        reject(new Error(\"Timed out publishing to observer relay.\"));\n      }, PUBLISH_TIMEOUT_MS);\n\n      this.publishes.set(event.id, { resolve, reject, timeout });\n\n      void this.sendRaw([\"EVENT\", event]).catch((error) => {\n        window.clearTimeout(timeout);\n        this.publishes.delete(event.id);\n        reject(\n          error instanceof Error\n            ? error\n            : new Error(\"Failed to publish to observer relay.\"),\n        );","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/shared/api/readOnlyRelayClient.ts#L100-L136","documentation":"The read-only (observer) relay client checks, immediately before sending a publish, that the socket still belongs to the same connection generation and has a live wsId. If a reconnect/session change happened between connect() and the send, or wsId is null, it refuses to publish. This prevents writes through a stale or dead socket.","triggerScenarios":"Calling publishEvent() while the observer socket is reconnecting, has been closed by a community switch, or the connection dropped after waitForRateLimit() yielded (generation changed or wsId became null).","commonSituations":"Publishing during a community switch; network drop mid-publish; rate-limit wait outlived the socket lifetime; publish attempted before the first successful connect completed.","solutions":["Wait for the client's connected state before calling publishEvent().","Retry publishEvent() after a short delay — connect() will re-establish the socket.","Check that only one community/session is driving the client at a time.","Subscribe to connection-state changes and queue publishes while disconnected."],"exampleFix":"// before\nawait client.publishEvent(event);\n// after\nawait client.connect();\ntry { await client.publishEvent(event); }\ncatch (e) { if (isNotConnected(e)) await retryWithBackoff(() => client.publishEvent(event)); else throw e; }","handlingStrategy":"retry","validationCode":"// poll client readiness before publishing\nwhile (!client.isConnected()) await new Promise(r => setTimeout(r, 250));\n","typeGuard":"function isNotConnectedError(e: unknown): boolean { return e instanceof Error && e.message === 'Read-only relay socket is not connected.'; }\n","tryCatchPattern":"try { await client.publishEvent(ev); } catch (e) { if (isNotConnectedError(e)) await retryWithBackoff(() => client.publishEvent(ev)); else throw e; }\n","preventionTips":["Await connect() and check connection state before publishing","Queue publishes during community switches","Use exponential backoff on retry, not tight loops"],"tags":["websocket","relay","race-condition"],"backgroundTag":"websocket-not-connected","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"}