Skip to content

fix(slack): list mcp servers off the event loop for the home tab - #24154

Merged
KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/slack-home-tab-list-servers-off-loop
Oct 26, 2026
Merged

KiroCrewCaptain merged 1 commit into
kirodotdev:mainfrom
IngridMorstrad:fix/slack-home-tab-list-servers-off-loop

Conversation

@IngridMorstrad

Copy link
Copy Markdown
Contributor

Problem / Motivation

Goal: Opening the bot's Slack App Home tab no longer blocks the gateway event loop while it lists the MCP servers.

_publish_home_tab (src/kiro_crew/slack/events_home_tab.py) called list_servers() inline on the event loop to build the Capabilities section. mcp_discovery_config.list_servers reads the agent config, <data home>/mcp.json and every provider's global config, including ~/.claude.json, which is routinely megabytes, and then merges the probe caches.

Why it matters

It runs each time any user opens the bot's Home tab. While it reads those files, every gateway session stalls; on a large ~/.claude.json or a slow data home that is a visible pause for everyone. This is the stall class behind #3057 and #1572.

Not a goal

  • Moving the other inline list_servers() calls in async code off the loop (see Sibling sweep).
  • Changing which servers the Home tab advertises.

What changed (motivation → approach → change)

The Home tab now lists MCP servers in a worker thread, the same way the line after it already lists skills and the way api_mcp_probe_cached, the Mochi routes and connections/ownership.py already call list_servers. The disabled filter applies to the result as before, so the tab shows the same servers.

The lexical sync-io gate does not count this site (the blocking IO is inside the imported helper), so no baseline line changes.

Backwards compatibility

Compatible: the same listing runs with the same arguments and the same filter; only the thread it runs on changes, and an exception from it still reaches the section's existing except.

Removes nothing: the diff moves one call into asyncio.to_thread and adds a test.

Tests

test_home_tab_mcp_server_listing_runs_off_loop in test/test_slack_home_tab.py stubs list_servers with a thread-recording function. It pins that the listing runs on a thread other than the loop's, runs once, and that a disabled server is still left out of the published view.

Red on the unfixed parent, for the right reason: assert threads[0] != loop_thread failed with assert 140331221796672 != 140331221796672 (the listing ran on the loop thread). Green on the fix, with all 32 tests in the file passing.

Proof: PROVEN test/test_slack_home_tab.py::test_home_tab_mcp_server_listing_runs_off_loop (head 63b0ad6)

Determinism: 20/20 runs of the new test at -n0; 10/10 runs at -n 4 --dist loadgroup --max-worker-restart=2 with test/test_slack_events_coverage.py and test/test_slack_sessions_view.py as co-tenants (258 passed each); the whole file in a shuffled order (seed 14252, 32 passed).

Local gates: black, isort, flake8, mypy, sync-io, subprocess-encoding, comment-history, brand, docs-lint and the flake-pattern ratchet all pass. scripts/local-gate.py backend related set: 232 passed.

Manual verification

N/A — unit coverage sufficient: the change is one thread hop around a pure listing call, and the test drives the real _publish_home_tab.

Related Issues

Closes #24153

Sibling sweep

Swept: git grep -nE "\blist_servers(" -- src/ (then each call site's enclosing function checked for async def)
Sibling: src/kiro_crew/slack/events_home_tab.py:121 -- changed here
Sibling: src/kiro_crew/dashboard/handlers/mcp_probe.py -- not affected -- by this diff; api_mcp_servers (line 426) and api_mcp_active (line 605) call list_servers inline, left for their own PR because open PR #24093 touches mcp_probe.py
Sibling: src/kiro_crew/mcp_discovery.py:141 -- not affected -- by this diff; probe_all calls list_servers inline, left for its own PR because open PRs #10175 and #22965 touch mcp_discovery.py

Pattern harvest

Rule candidate: lint
Pattern: an async def calling an imported sync helper that does filesystem IO; the lexical sync-io gate sees only direct fs calls, so a cross-module scan (async caller -> imported sync function -> fs within a few hops) would catch this class.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

_publish_home_tab called mcp_discovery.list_servers() inline on the
event loop. The listing reads the agent config, <data home>/mcp.json
and every provider's global config (including ~/.claude.json, which is
routinely megabytes), so each time a user opened the bot's Home tab
the gateway loop stalled for every session. The next line already
lists skills through asyncio.to_thread, and every other async caller
of list_servers offloads it the same way.

Run list_servers through asyncio.to_thread and keep the disabled
filter on its result. Add a thread-recording list_servers stub test to
test/test_slack_home_tab.py that pins the listing off the loop thread
and that disabled servers are still not advertised.
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running labels Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Opus 5.5, fork) — ⏭️ skipped

Revision 63b0ad6aebdd9837aa7cb9f2d52138caed7e58c5 touches no user-facing surface (no changes under website/ or committed screenshots), so the UX review was skipped. Does not block merge.

@github-actions

Copy link
Copy Markdown
Contributor

First Principles Review (Opus 5.5, fork) — ✅ PASS

Premise-level review of 63b0ad6aebdd9837aa7cb9f2d52138caed7e58c5 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

First-Principles-Verdict: PASS

Nothing blocks. The fix is one thread hop at the mechanism. The other blocking list_servers() callers are named and deferred under ## Not a goal.

What this change ships

Inventory (2 items) — 2 justified

Intent: let someone open the bot's Slack Home tab without stalling every other gateway session (a FIX, closes #24153).

  1. When a user opens the Home tab, the MCP server listing now runs in a worker thread instead of on the event loop. — justified
  2. New test pins that the listing runs off the loop thread and that disabled servers stay hidden. — justified

[FIRST-PRINCIPLES-REVIEWED] 63b0ad6

Description read

[DESCRIPTION-READ] fc2ce0ca3208de9f5e2d632b523d68c8d4d1608e5be007048fe9e17512e451e1

That sha256 names the PR description this verdict read. Recompute it with .github/scripts/pr-description-capture.sh; if it differs, the description changed after this verdict was formed and any finding drawn from the description is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Design Review (Opus 5.5, fork) — ✅ PASS

Design-level review of 63b0ad6aebdd9837aa7cb9f2d52138caed7e58c5 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS does not.

Design-Verdict: PASS

Nothing blocks: it moves one blocking listing call into a worker thread, the same way the skills listing on the next line already runs, and it applies the same filter to the result.

[DESIGN-REVIEWED] 63b0ad6

Description read

[DESCRIPTION-READ] a666091ccae644539c32b78f9772aa83958975137bd7a02e68388265e021a29a

That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with .github/scripts/pr-description-capture.sh over the same inputs; if it differs, the description or that evidence changed after this verdict was formed and any finding drawn from either is unproven.

@github-actions

Copy link
Copy Markdown
Contributor

Opus 5.5 Review (fork) — ✅ no blocking findings

Reviewed 63b0ad6aebdd9837aa7cb9f2d52138caed7e58c5 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] 63b0ad6

@github-actions

Copy link
Copy Markdown
Contributor

GPT 6.1 Review (fork) — ✅ no blocking findings

Reviewed 63b0ad6aebdd9837aa7cb9f2d52138caed7e58c5 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 63b0ad6

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Oct 26, 2026
@KiroCrewCaptain
KiroCrewCaptain enabled auto-merge (squash) October 26, 2026 02:03

@KiroCrewCaptain KiroCrewCaptain left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (2 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, security checklist all-NO, no reviewer-lane findings, readiness signal fresh for this head SHA, declared category matches the diff. Category: the Slack Home tab lists MCP servers in a worker thread instead of on the event loop, with a test that the disabled filter still applies.

@KiroCrewCaptain
KiroCrewCaptain merged commit 586181e into kirodotdev:main Oct 26, 2026
144 of 146 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

slack: App Home tab lists MCP servers on the event loop

2 participants