Skip to content

Desktop hardcodes HOSTED_COMMUNITY_LIMIT = 5 while the relay enforces the env-configurable max_communities_per_owner() — UI drifts in both directions once BUZZ_MAX_COMMUNITIES_PER_OWNER is set #4160

Description

@tlongwell-block

Problem

The per-owner community limit is enforced by the relay from an env-configurable value but gated in the desktop UI by a hardcoded constant. The two agree today only because both were edited by hand in the same PR. The moment a deployment sets BUZZ_MAX_COMMUNITIES_PER_OWNER, the client and the server disagree and the UI is wrong in one of two directions.

Server (authoritative, env-configurable): crates/buzz-db/src/relay_members.rs:414 max_communities_per_owner()effective_owner_limit(env BUZZ_MAX_COMMUNITIES_PER_OWNER), falling back to MAX_COMMUNITIES_PER_OWNER = 5 (relay_members.rs:406) only when the env var is absent/unparseable/non-positive (relay_members.rs:428-432). Enforced in both write paths: create (crates/buzz-db/src/lib.rs:1440) and transfer (relay_members.rs:528).

Client (hardcoded): desktop/src/features/communities/hostedCommunityApi.ts:4 export const HOSTED_COMMUNITY_LIMIT = 5;

Gating sites, all reading that constant:

File:line Behavior driven by the constant
hostedCommunityApi.ts:72 limit_reached error copy: "You've reached the limit of 5 hosted communities."
HostedCommunityCreateFlow.tsx:192 atCommunityLimit → disables the name input (:411) and the submit button (:447), and short-circuits create() (:225)
HostedCommunityCreateFlow.tsx:375 Feedback copy "the limit of 5"
HostedCommunityOnboarding.tsx:243 atCommunityLimit → same disable/short-circuit pattern
HostedCommunityOnboarding.tsx:356 Feedback copy
HostedCommunitiesSettingsCard.tsx:363 Blocks createCommunity()
HostedCommunitiesSettingsCard.tsx:415 atCommunityLimit
HostedCommunitiesSettingsCard.tsx:558 Renders "N of 5 used"
HostedCommunitiesSettingsCard.tsx:622 "You've reached the limit of 5 hosted communities."

Both failure directions

Env set below 5 (e.g. BUZZ_MAX_COMMUNITIES_PER_OWNER=3): the client happily lets the user name and submit a 4th community. The relay rejects it, and the desktop renders the mapped limit_reached copy — which says "the limit of 5". The user is told a number that is not the limit they just hit. The settings card also reads "3 of 5 used" while they are in fact maxed out.

Env set above 5 (e.g. 7): the client caps at 5 — input disabled, button disabled, "5 of 5 used" — while the relay would have accepted two more. The operator raised the limit and the UI silently ignores it, which is exactly the multi-tenant case #2600 asked for and #2599 shipped.

Why this is not a one-line fix

The number is nowhere on the wire. The client cannot learn the effective limit today:

  • GET /operator/communities (crates/buzz-relay/src/api/operator.rs:331-339) returns owner_pubkey + communities[] (community_id, host, created_at, archived_at) — no limit field.
  • ProvisionCommunityResponse (crates/buzz-relay/src/handlers/community_provisioning.rs:57-69) has community_id, host, status, owner_pubkey — no limit field.
  • The rejection string carries no number: "limit_reached: owner already owns the maximum number of communities" (community_provisioning.rs:296) and the transfer twin at operator.rs:426.

So an honest fix is an API-shape change plus a UI read: expose the effective limit on a response the client already calls (the list endpoint is the natural home — it is what populates communities.length), type it in HostedCommunitiesResponse (hostedCommunityApi.ts:39-43), and have the three gating sites read it with the constant demoted to a fallback for pre-upgrade servers. That is production code touching a public response shape, so it wants its own review — deliberately not folded into #4148, which is test-only and green.

Defect family

This is the third instance of one shape: a test or client hardcodes a value the code derives.

  1. crates/buzz-db/src/lib.rs owner-limit test seeded 0..3 while the code enforced the function — broke when feat(relay): raise hosted community limit to five #3829 raised the default. Fixed in test(db): pin the fence verdict guard; fix the two #3829-broken owner-limit tests #4148.
  2. crates/buzz-db/src/relay_members.rs transfer-limit test, same. Fixed in test(db): pin the fence verdict guard; fix the two #3829-broken owner-limit tests #4148.
  3. crates/buzz-relay/src/api/operator.rs:1086 seeded from the const while the code enforces the function — survived feat(relay): raise hosted community limit to five #3829 because the const bump moved test and code together, but inverts as soon as the env is set. Found post-open, fixed in test(db): pin the fence verdict guard; fix the two #3829-broken owner-limit tests #4148 (7db75a607); Eva RED/GREEN-proved it at BUZZ_MAX_COMMUNITIES_PER_OWNER=3.
  4. This issue — the same split, in production code rather than tests.

The generalizable rule: when a value becomes runtime-configurable, every consumer that duplicates it becomes a latent bug, and the const left behind as a default makes the duplication invisible to review. #2599 made the limit configurable; #3829 then edited the const in two files by hand — which is the tell.

Repro (requires wiring the env)

  1. Start the relay with BUZZ_MAX_COMMUNITIES_PER_OWNER=3.
  2. As one owner, create 3 hosted communities from the desktop settings card.
  3. The card shows "3 of 5 used" and leaves the create form enabled.
  4. Submit a 4th with a fresh, available name → relay 409 limit_reached, and the desktop renders "You've reached the limit of 5 hosted communities."

Note step 1 is the reason this is latent: BUZZ_MAX_COMMUNITIES_PER_OWNER is set nowhere in this repo. I grepped the whole working tree (excluding target/) and it appears only in relay_members.rs — the reader itself plus its own unit tests. No chart, template, workflow, or compose file sets it. Same "latent until someone wires the env" posture as BUZZ_REPLICA_READ_MAX_AGE_MS in #4124.

Caveat I could not close

The desktop does not call the relay's operator API directly. It calls Builderlab — desktop/src-tauri/src/builderlab.rs:15 (https://app.builderlab.xyz/api/goose), via POST /v1/buzz/communities/list (builderlab.rs:550) and POST /v1/buzz/communities (builderlab.rs:582). That service is not in this repo and I did not read it. I verified the relay emits the limit_reached: prefix and the desktop maps a limit_reached code, but I cannot say whether the middle hop already forwards a limit value or normalizes the error. Anyone implementing the fix should check the Builderlab side first — if it already has the number, the change may be smaller than described; if it does not, it needs a change too.

Non-findings, checked so they don't get re-litigated

  • Archived communities are counted consistently on both sides. The server counts relay_members WHERE role = 'owner' regardless of archived_at (lib.rs:1433-1438, relay_members.rs:521-527), and archive_community_owned_by only sets archived_at without touching the owner row (lib.rs:1486-1507). The client uses communities.length, i.e. also includes archived. So the settings-card copy "Transfer one to free up a slot" is correct and archiving-vs-counting is not part of this bug.
  • Onboarding's activeCommunities filter is display-only. HostedCommunityOnboarding.tsx:236-238 filters archived for rendering, but :243 gates on unfiltered communities.length — which matches the server. Not a defect.

Provenance

Found while verifying #4148 (follow-up to #4124). All file:line references read at #4148 head 7db75a607 via git show, in REPOS/buzz-pr4124-sami. Related: #2600 (asked for configurability), #2599 (shipped BUZZ_MAX_COMMUNITIES_PER_OWNER), #3829 (raised both hardcoded values by hand), #4148 (fixed instances 1-3).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions