Skip to content

fix(desktop): badge relay-admitted agents as channel members - #6919

Open
arakakileo wants to merge 3 commits into
block:mainfrom
arakakileo:fix/remote-agent-membership-badge
Open

fix(desktop): badge relay-admitted agents as channel members#6919
arakakileo wants to merge 3 commits into
block:mainfrom
arakakileo:fix/remote-agent-membership-badge

Conversation

@arakakileo

Copy link
Copy Markdown

Summary

  • Treat relay-directory agents whose channelIds contains the active channel as channel members in mention autocomplete.
  • Preserve the “not in channel” badge for managed agents that are genuinely outside the roster.
  • Add regression coverage for stale roster caches and deterministic teardown for React Query/JSDOM fixtures.

Test plan

  • node --import ./test-loader.mjs --experimental-strip-types --test src/features/messages/lib/useMentionsRemoteMembership.test.mjs — 3/3
  • pnpm test — 5675/5675
  • pnpm typecheck
  • pnpm exec biome check on the three changed files
  • git diff --check origin/main...HEAD

Integration

  • Integrated onto block/buzz@b622003f7.
  • Verified integrated head: bab1b08f31ebbb05ae558b5bfb62ad132c377ca4.
  • Origin: Buzz #Infra channel 7ac8715c-4f45-4c24-8f15-0f1e6289c854.

Risk / rollback

  • Low risk: product delta is scoped to mention membership presentation.
  • Rollback by reverting the three commits; no migration, data, or credentials.

…n mention autocomplete

An agent managed on another install that joins a channel appears in the
relay's live agent directory with that channel in channelIds — the same
relay-signed fact that admits it into autocomplete
(relayAgentCanRespondInChannel) and authorizes mention delivery. But the
relay-agent candidate loop in useMentions hardcoded isMember: false, and
mentionSuggestionMapping derives the notInChannel badge from
isMember === false, so while the member-roster cache was stale (up to
CHANNEL_MEMBERS_STALE_TIME_MS = 5 min) an admitted, deliverable agent was
badged "not in channel" in the very channel it had joined.

Derive isMember for relay-agent candidates from the directory's
channelIds in channel-scoped mention surfaces (stream/forum). Outside
channel-scoped surfaces the badge keeps reflecting the roster only.

Adds useMentionsRemoteMembership.test.mjs driving the real hook through a
stale-roster repro: directory-confirmed member must not carry the badge,
fresh-roster and managed-outside-channel cases pin the fix's scope.

Signed-off-by: arakakileo <leooarakaki@gmail.com>
GATE 1 rework for the remote-agent membership badge fix.

The new useMentionsRemoteMembership suite passed in ~1s but its node --test
process only exited after ~302s without --test-force-exit. Instrumented
timer bookkeeping shows the survivors: twelve 300000ms GC timers from
@tanstack/query-core, scheduled by Query.removeObserver -> scheduleGc when
each test's last observer unmounts. cleanup() unmounts React but never
touches the QueryClient, so the GC timers keep the event loop alive for
the full 5 minutes.

Track the per-test QueryClients and clear() them in afterEach:
client.clear() removes all queries, and Removable.destroy() cancels the
pending GC timeout. Focal runner now exits 0 in ~8s with no force-exit.

The full-suite run then exposed a pre-existing flake in
useDocumentVisible.test.mjs ("focused polling pauses on blur and resumes
after activation yields"): the resume notification is delivered through
scheduleAfterForegroundReady's chained 0ms timers, and under full-suite
load those timers exceed the test's fixed 10ms real-time wait. Proven
base flake: ceb8ba6 reproduces the identical failure signature (1/4
runs, same AssertionError + post-teardown "window is not defined" from
the late trailing task). Make the wait deterministic by polling for the
resume with a 5s deadline instead of a constant sleep, and bail out if
the window global was already restored so a late resume cannot throw
into an unrelated test's output.

Full desktop suite without --test-force-exit: 3x consecutive runs exit 0
(5674/5674 each). biome, tsc --noEmit, and git diff --check green.

Signed-off-by: arakakileo <leooarakaki@gmail.com>
…ll IPC

GATE 1 rework round 2 for the remote-agent membership badge fix.

The Friday gate proved timers still active after client.clear() at
fe78a2b19: in-flight queries re-arm stale/GC timers. Root cause is an
ordering race in @tanstack/query-core 5.100.14: Query.fetch() ends with
`finally { this.scheduleGc() }`, so a fetch that settles AFTER clear()
re-arms a 300s GC timer on a query that clear() already removed from the
cache — nobody ever calls destroy() on it again, and the timer holds the
event loop open.

Fix, in afterEach, in order:
- await client.cancelQueries() first: settles every in-flight fetch
  (the CancelledError path also runs the finally) while the queries are
  still cache-resident, so scheduleGc() lands before removal;
- await client.clear() after: remove() -> destroy() -> clearGcTimeout()
  now finds and cancels every GC timer.

The remaining ~5s post-assertion tail was a separate leak: mediaUrl.ts
starts a shared proxy-port poll at module load (it sees the JSDOM
window), and the fixture only installed IPC handlers after the
production imports, so the poll spun its full 5s deadline on rejected
invokes. Install static success handlers for get_relay_http_url and
get_media_proxy_port before any production import so the poll resolves
on its first iteration. Also close the JSDOM window in after() so the
dom itself cannot keep the loop referenced.

Evidence at this SHA:
- focal 3/3 exit 0, wall ~4.3s each (was ~8s), no force-exit;
  timer-leak probe shows zero surviving timers >=100ms;
- full desktop suite 3x consecutive: 5674/5674 exit 0, wall 120-125s,
  no force-exit;
- biome check and tsc --noEmit clean.

Signed-off-by: arakakileo <leooarakaki@gmail.com>
@arakakileo
arakakileo requested a review from a team as a code owner August 27, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant