Summary
Changing a community's configured Relay URL to a different string — even when it points at the exact same relay — silently orphans the existing Welcome Team (Fizz/Honey/Bumble) and mints a brand-new trio with fresh keypairs. This is a distinct root cause from #2648 (second Desktop install) and #3639 (forceNewInstance: true on "Deploy team to channel"): no second install and no manual team-deploy involved, just an ordinary relay URL edit.
Root cause
Two places do a bare exact-string comparison on the relay URL, with no canonicalization anywhere in the path:
hasEnsuredWelcomeChannel / markWelcomeChannelEnsured (desktop/src/features/onboarding/welcome.ts:284-324) build a localStorage key by embedding communityScope (= activeCommunity.relayUrl, unmodified) verbatim via welcomeChannelEnsuredStorageKey. A different string for the same relay → cache miss → treated as a never-provisioned community.
isAgentScopedToRelay (desktop/src/features/onboarding/welcomeGuide.ts:56-62), used by pickWelcomeTeamStarterAgentForRelay, compares normalizeRelayUrl(agent.relayUrl) === normalizeRelayUrl(targetRelayUrl) — normalizeRelayUrl only trims trailing slashes, it doesn't resolve hostnames vs IPs vs schemes to a canonical identity. An existing Fizz/Honey/Bumble stamped with one string is invisible to a lookup using a different (but logically identical) string.
The re-provisioning path itself is useAppOnboardingState's effect in desktop/src/features/onboarding/hooks.ts:578-595, which calls requestStarterChannels(false) → initializeStarterChannels → seedWelcomeExperience → ensureWelcomeTeam (welcomeGuide.ts:343-356 → provisionWelcomeTeam) whenever hasEnsuredWelcomeChannel misses for the current communityScope. provisionWelcomeTeam (welcomeGuide.ts:268-341) does look for an existing agent per persona via pickWelcomeTeamStarterAgentForRelay before creating one — but since that lookup is also keyed on the (mismatched) relay URL string, it never finds the existing instance and calls createManagedAgent instead, minting a new keypair.
Real-world reproduction (not a synthetic repro — this is what actually happened on one Desktop install)
Self-hosted single-user community, single Desktop install, no second machine, no manual "Deploy team to channel" click. managed-agents.json currently has 3 records each for Bumble/Fizz/Honey (9 total for what should be 3), in three timestamp clusters, each stamped with a different relay_url:
relay_url on the created agents |
Created at |
Trigger |
ws://localhost:3000 |
2026-07-22T19:28 |
First-run default (defaultRelayUrl in useCommunityInit.ts), before the real relay was configured |
ws://100.119.50.98:3000 (a raw Tailscale IP, non-TLS, port 3000) |
2026-07-27T21:33 |
Community's Relay URL edited to point at the relay via its Tailscale IP directly |
wss://<tailscale-magicdns-hostname>:8443 (the final, correct address, TLS-terminated via tailscale serve) |
2026-07-27T21:39 (six minutes later) |
Community's Relay URL edited again, to the final hostname, while wiring up mobile pairing that same session |
Every persona has a distinct pubkey per cluster. The two abandoned clusters are stale: never restarted since creation (last_started_at: null), sitting as dead bot members in the private "Welcome" channel with the same display names as the live ones, indistinguishable in the @mention picker (same failure mode independently reported in #2648 by AI-OWEN).
The user reported having removed the stale channel members once already; they reappeared "a few minutes later" — which lines up exactly with the two edits six minutes apart above, not a background process re-triggering on every app launch. Desktop has since run 13+ continuous hours without the relay URL changing again, and no further duplicate cluster has appeared, consistent with the trigger being the URL edit itself rather than a boot-time race.
Expected behavior
Editing a community's Relay URL should not orphan and re-mint the Welcome Team. Options:
- Resolve/canonicalize relay identity by community ID (already the real join key elsewhere in the app) rather than the raw URL string, for both the
localStorage ensured-marker and the agent-reuse lookup.
- At minimum, when a Relay URL is edited for an existing community, offer to re-point (not re-mint) the existing Welcome Team agents at the new URL, the same way other community-scoped state gets migrated on a relay change.
Related
Summary
Changing a community's configured Relay URL to a different string — even when it points at the exact same relay — silently orphans the existing Welcome Team (Fizz/Honey/Bumble) and mints a brand-new trio with fresh keypairs. This is a distinct root cause from #2648 (second Desktop install) and #3639 (
forceNewInstance: trueon "Deploy team to channel"): no second install and no manual team-deploy involved, just an ordinary relay URL edit.Root cause
Two places do a bare exact-string comparison on the relay URL, with no canonicalization anywhere in the path:
hasEnsuredWelcomeChannel/markWelcomeChannelEnsured(desktop/src/features/onboarding/welcome.ts:284-324) build alocalStoragekey by embeddingcommunityScope(=activeCommunity.relayUrl, unmodified) verbatim viawelcomeChannelEnsuredStorageKey. A different string for the same relay → cache miss → treated as a never-provisioned community.isAgentScopedToRelay(desktop/src/features/onboarding/welcomeGuide.ts:56-62), used bypickWelcomeTeamStarterAgentForRelay, comparesnormalizeRelayUrl(agent.relayUrl) === normalizeRelayUrl(targetRelayUrl)—normalizeRelayUrlonly trims trailing slashes, it doesn't resolve hostnames vs IPs vs schemes to a canonical identity. An existing Fizz/Honey/Bumble stamped with one string is invisible to a lookup using a different (but logically identical) string.The re-provisioning path itself is
useAppOnboardingState's effect indesktop/src/features/onboarding/hooks.ts:578-595, which callsrequestStarterChannels(false)→initializeStarterChannels→seedWelcomeExperience→ensureWelcomeTeam(welcomeGuide.ts:343-356→provisionWelcomeTeam) wheneverhasEnsuredWelcomeChannelmisses for the currentcommunityScope.provisionWelcomeTeam(welcomeGuide.ts:268-341) does look for an existing agent per persona viapickWelcomeTeamStarterAgentForRelaybefore creating one — but since that lookup is also keyed on the (mismatched) relay URL string, it never finds the existing instance and callscreateManagedAgentinstead, minting a new keypair.Real-world reproduction (not a synthetic repro — this is what actually happened on one Desktop install)
Self-hosted single-user community, single Desktop install, no second machine, no manual "Deploy team to channel" click.
managed-agents.jsoncurrently has 3 records each for Bumble/Fizz/Honey (9 total for what should be 3), in three timestamp clusters, each stamped with a differentrelay_url:relay_urlon the created agentsws://localhost:3000defaultRelayUrlinuseCommunityInit.ts), before the real relay was configuredws://100.119.50.98:3000(a raw Tailscale IP, non-TLS, port 3000)wss://<tailscale-magicdns-hostname>:8443(the final, correct address, TLS-terminated viatailscale serve)Every persona has a distinct pubkey per cluster. The two abandoned clusters are stale: never restarted since creation (
last_started_at: null), sitting as deadbotmembers in the private "Welcome" channel with the same display names as the live ones, indistinguishable in the@mentionpicker (same failure mode independently reported in #2648 byAI-OWEN).The user reported having removed the stale channel members once already; they reappeared "a few minutes later" — which lines up exactly with the two edits six minutes apart above, not a background process re-triggering on every app launch. Desktop has since run 13+ continuous hours without the relay URL changing again, and no further duplicate cluster has appeared, consistent with the trigger being the URL edit itself rather than a boot-time race.
Expected behavior
Editing a community's Relay URL should not orphan and re-mint the Welcome Team. Options:
localStorageensured-marker and the agent-reuse lookup.Related
forceNewInstance: trueon Deploy team to channel (different trigger, same class of symptom)