Skip to content

fix(desktop): stop the Bots roster hanging on a live profile - #92793

Closed
kudapara wants to merge 5 commits into
NousResearch:mainfrom
kudapara:fix/bots-roster-loading
Closed

kudapara wants to merge 5 commits into
NousResearch:mainfrom
kudapara:fix/bots-roster-loading

Conversation

@kudapara

@kudapara kudapara commented Aug 23, 2026

Copy link
Copy Markdown

The Bots tab could sit empty on a spinner while you were already talking to a bot, or land on Roster unavailable: activeBotRoute is not defined. The roster now keeps listing your agents during a live turn; a missing route falls through to the local profile list instead of crashing the pane.

profiles.list was opening every profile state.db as a writer (up to 20s of lock patience, twice) while that bot's backend held the lock, so the desktop RPC timed out. Bot Mode then retried forever, which React Query treats as still-loading. Bounded retries then surfaced a second bug: useRoster still called activeBotRoute() after that helper was dropped from the plugin.

image

Validation

  • scripts/run_tests.sh tests/tui_gateway/test_profiles_list_canonical_session.py tests/tui_gateway/test_profiles_list_worker_session.py — 13 passed, including a held write-lock bound
  • node --test src/plugins/hermes-bots/tests/roster-query-retry.test.mjs src/plugins/hermes-bots/tests/hide-bots.test.mjs src/plugins/hermes-bots/tests/canonical-chat-registry.test.mjs — 24 passed
  • Manual: rebuilt desktop; BOTS listed Hermes, game-night, Game Night…, and mpowa-swe with no error card

Compound Engineering
grok-4.6

profiles.list opened every profile state.db as a writable SessionDB,
which waits out write-lock patience while that profile's backend is
mid-turn. The desktop RPC timed out and Bot Mode's infinite React
Query retry kept the sidebar on a spinner.

Inspect those DBs read-only and bound roster retries so names still
paint.
The hang bound alone would pass if read-only open degraded to None.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 23, 2026
useRoster called a helper that was dropped after the remote-routing
commit. Bounded retries then surfaced ReferenceError as "Roster
unavailable". Define the helper again and treat a missing route as
the local profiles.list door.
Drop the plugin.js source regex (AGENTS.md: never read source in tests)
and lock ROSTER_QUERY_RETRY at 2. Exercise matching, thrown, and
no-match profileRoutes so the restore cannot stay green while unused.
- useRoster and the hide-sweep call host.request('profiles.list') on the
  active socket instead of waiting on activeBotRoute / refreshProfiles
  (60s /api/profiles) before first paint
@kudapara

Copy link
Copy Markdown
Author

useRoster awaits activeBotRoute() before profiles.list; that call can wait on refreshProfiles() (GET /api/profiles, 60s budget)

Addressed differently: first-paint roster no longer waits on route inventory at all. useRoster and the hide-sweep call host.request('profiles.list') on the already-live socket (same backend requestProfile would hit for the active window). Other connections still arrive via host.agents(). activeBotRoute stays for tests / future callers.

@T-Y89

T-Y89 commented Aug 24, 2026

Copy link
Copy Markdown

Hi @kudapara
first of all, thank you for this PR. I hit what looks like the same "Bots pane stuck on an empty spinner" symptom today, and while debugging it independently I found your PR. Evidence below, plus one observation about how your patch interacts with the fix that has since landed on main.

Quick caveat: I'm not affiliated with Nous and can't really review the code.
these are just one user's debugging notes, in case they're useful.

What I observed (v0.20.5 packaged desktop, macOS arm64)

Chat over the same gateway worked fine the whole time, so the backend was healthy — only the roster render path was dead.

1. React Query roster cache frozen in fetching, never resolving:

// via CDP → Runtime.evaluate, walking the fiber tree to the queryClient
{
  "key": ["hermes-bots", "roster", "local"],
  "status": "pending",
  "fetchStatus": "fetching",
  "error": null,
  "data": null
}
// same state after manual query.fetch() + 6s wait; survives app restarts

2. The profiles.list RPC was never sent — not timed out, never issued:

# CDP → Network domain, WS frame capture while forcing a refetch:
#   frames containing "profiles": []        ← zero matches over 9s
#   frames seen in the same window (other RPCs flowing fine):
RECV {"jsonrpc":"2.0","id":209,"result":{"sessions":[...]}}
SENT ...
RECV {"jsonrpc":"2.0","id":210,"result":{"enabled":false}}

That ruled out the lock-timeout path for my instance and pointed at something dying inside the queryFn before the first request.

3. Cause: merge 0404020f7 dropped the helper's definition but kept both call sites:

$ git show 38ce2d755:apps/desktop/src/plugins/hermes-bots/plugin.js | grep -c activeBotRoute
0                      # main-side parent: definition already gone

$ git show ddc78217f:apps/desktop/src/plugins/hermes-bots/plugin.js | grep -c activeBotRoute
3                      # branch-side parent: definition + 2 uses survive

# merge 0404020f7 kept the 2 uses without the definition → ReferenceError,
# swallowed by React Query's retry:true loop → eternal spinner, no error card

4. Renderer-side confirmation of the throw (the pane never surfaces it):

// window.hermesDesktop.api(...) against the healthy local backend answered
// instantly when called directly — only the plugin's queryFn was dead:
{"baseUrl":"http://127.0.0.1:49835","mode":"local","authMode":"token", ...}

5. Updating to current main fixed it immediately:

$ git log --oneline -S activeBotRoute origin/main -- apps/desktop/src/plugins/hermes-bots/plugin.js
2ec229ec5 fixup: roster query keeps SDK ambient owner route ...   # removes the calls
8523819fc refactor(desktop): consume upstream Bot owner routing
9b7ab9d65 feat(desktop): route remote bot actions by connection   # introduced them

Bot definitions in profile.yaml were intact throughout (profiles.list returns all rows with ui_meta.hermes-bots.title when called directly) — data was never lost, only the render path was dead.

What this means for this PR (a humble suggestion)

  • Patch 3 ("restore activeBotRoute") may now be superseded: current main contains zero activeBotRoute references, so restoring the helper would reintroduce code upstream has since removed by a different approach. Rebasing onto latest main will likely shrink the diff to just the parts still needed.
  • Patch 1 (profiles.list opening profile state.dbs read-only) is still valuable and worth landing, IMO. My instance didn't exercise that path, but your diagnosis matches the architecture: a live writer holding the DB lock would stall roster reads even with the ReferenceError gone. The bounded-retry change also fixes a real UX gap — retry: true keeps React Query in isLoading forever with no error affordance, which is exactly the no-feedback state I sat in.

If it helps triage: the two failure modes have distinguishable wire signatures. Merge-regression cases show zero profiles.list RPCs on the socket; lock-contention cases should show requests issued and timing out. Happy to help verify either path on my machine.

Thanks again
the test coverage in this PR (roster retry bound, read-only DB open, canonical-session-under-writer) is exactly the kind this area needs.

Note: I do QA professionally; hoping to contribute repro/verification help here going forward.

@teknium1

Copy link
Copy Markdown
Collaborator

Merged via #95126 (rebase-merge) — your two commits are on main as-authored: ad1d159. Thanks @kudapara — earliest submitter in the #92830 cluster, and the write-lock diagnosis was exactly right.

Scope note: we took the methods_profiles.py read-only refactor and the bounded roster retry (the two halves main still lacked). The activeBotRoute plugin hunks were dropped — main removed that path entirely in 2ec229e in favor of SDK ambient-owner routing. An integration commit reconciles your read-only open with the #92687 canonical-chat resurrect that landed after you filed: recoverability is judged read-only first, and the un-archive write uses a short-lived writable handle only in the rare accidental-archive case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/desktop Electron desktop app (apps/desktop/*) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants