{"record":{"id":"8c1a980130b13280","repo":"openclaw/openclaw","slug":"missing-buzz-room-membership-for-channelid","errorCode":null,"errorMessage":"Missing Buzz room membership for ${channelId}","messagePattern":"Missing Buzz room membership for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/buzz/src/room-membership-tracker.ts","lineNumber":161,"sourceCode":"        await queryBuzzRoomMemberships({\n          relay: params.relay,\n          relayPublicKey: params.relayPublicKey,\n          channelIds: [channelId],\n          signal: params.signal,\n        })\n      ).get(channelId),\n    );\n    membershipQueryTail = query.then(\n      () => undefined,\n      () => undefined,\n    );\n    return query;\n  };\n\n  const refreshMembership = async (channelId: string, state: RefreshState): Promise<void> => {\n    const baseline = memberships.get(channelId);\n    if (!baseline) {\n      throw new Error(`Missing Buzz room membership for ${channelId}`);\n    }\n    for (const delayMs of MEMBERSHIP_REFRESH_DELAYS_MS) {\n      const generation = state.generation;\n      state.lastAttemptedGeneration = generation;\n      await sleepWithSignal(delayMs, params.signal);\n      if (state.generation !== generation) {\n        continue;\n      }\n      let refreshed: BuzzRoomMembership | undefined;\n      try {\n        refreshed = await queryMembership(channelId);\n      } catch (error) {\n        if (params.signal?.aborted) {\n          throw error;\n        }\n        continue;\n      }\n      if (state.generation !== generation || !refreshed) {","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/buzz/src/room-membership-tracker.ts#L143-L179","documentation":"Thrown inside refreshMembership when the baseline membership for a channelId is absent from the memberships map. refreshMembership is only valid for channels already seeded with an initial membership; calling it for an unseeded or evicted channel is an internal invariant violation.","triggerScenarios":"refreshMembership invoked for a channelId whose membership was never loaded, was deleted, or whose initial load failed before refresh started. Indicates a lifecycle/ordering bug in the tracker rather than user input.","commonSituations":"A race where a room is removed from the configured set concurrently with a pending refresh, or a prior startup failure left the memberships map partially populated.","solutions":["Ensure initial memberships for all configured channelIds are loaded before scheduling refreshes.","Guard refresh scheduling against rooms removed from the active configuration.","Treat a missing baseline as a no-op (skip refresh) rather than throwing if the room is no longer configured."],"exampleFix":"// before\nconst baseline = memberships.get(channelId);\nif (!baseline) {\n  throw new Error(`Missing Buzz room membership for ${channelId}`);\n}\n\n// after - skip refresh for unseeded/evicted rooms\nconst baseline = memberships.get(channelId);\nif (!baseline) {\n  return; // room no longer tracked; nothing to refresh\n}","handlingStrategy":"validation","validationCode":"function hasBaselineMembership(\n  memberships: Map<string, unknown>,\n  channelId: string,\n): boolean {\n  return memberships.has(channelId);\n}\n\nif (!hasBaselineMembership(memberships, channelId)) {\n  // seed membership or skip refresh\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always seed memberships for all configured channels before scheduling refreshes.","Cancel in-flight refreshes when a room is removed from the active configuration.","Treat a missing baseline as a no-op refresh rather than a fatal error."],"tags":["state","invariant","race-condition","buzz","room-membership"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}