claude-code - 💡(How to fix) Fix [BUG] /mcp` modal freezes TUI hard on WSL2 — only reproducible without `--debug [2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#52882Fetched 2026-04-25 06:18:21
View on GitHub
Comments
2
Participants
3
Timeline
9
Reactions
0
Author
Timeline (top)
labeled ×5commented ×2cross-referenced ×2

Error Message

Error Messages/Logs

No error is printed: the TUI renders the menu successfully and then silently becomes unresponsive. Because --debug is off when the bug reproduces, there are no debug logs to attach — this is part of what makes the bug hard to diagnose from user logs.

Root Cause

No error is printed: the TUI renders the menu successfully and then silently becomes unresponsive. Because `--debug` is off when the bug reproduces, there are no debug logs to attach — this is part of what makes the bug hard to diagnose from user logs.

Code Example

No error is printed: the TUI renders the menu successfully and then silently becomes unresponsive. Because `--debug` is off when the bug reproduces, there are no debug logs to attach — this is part of what makes the bug hard to diagnose from user logs.

Instead, here is a **kernel-side snapshot** captured on a live frozen process (PID 1376, `claude --resume`, 35s elapsed, stuck on `/mcp`):

### Threads


$ ps -T -p 1376 -o tid,stat,%cpu,wchan:20,comm
  TID STAT %CPU WCHAN                COMMAND
 1376 Sl+  10.8 do_epoll_wait        claude         <- main thread
 1377 Sl+   0.4 futex_wait_queue     claude
 1378 Sl+   1.0 futex_wait_queue     HeapHelper
 1379 Sl+   1.0 futex_wait_queue     HeapHelper
 1380 Sl+   0.9 futex_wait_queue     HeapHelper
 1381 Sl+   0.9 futex_wait_queue     HeapHelper
 1382 Sl+   0.9 futex_wait_queue     HeapHelper
 1383 Sl+   0.9 futex_wait_queue     HeapHelper
 1384 Sl+   0.9 futex_wait_queue     HeapHelper
 1385 Sl+   2.2 futex_wait_queue     JITWorker
 1386-1399 Sl+ ~0 futex_wait_queue    Bun Pool 0-7
 1397 Sl+   0.0 do_epoll_wait        HTTP Client
 1445 Sl+   0.0 wait_woken           File Watcher


### Process state


$ cat /proc/1376/status | grep -E 'State|Threads|ctxt'
State: S (sleeping)
Threads: 20
voluntary_ctxt_switches:    28428    <- in ~35s of uptime = ~810/s
nonvoluntary_ctxt_switches: 36


### I/O activity (delta over 3 seconds)


rchar:       +11306 bytes   (~3.7 KB/s reads from TTY/sockets)
wchar:       +7759  bytes   (~2.6 KB/s writes to TTY)
syscr:       +333           (~111 read syscalls/s)
syscw:       +97            (~32 write syscalls/s)   <- TTY redraw at ~30 fps
read_bytes:  +0             (no disk reads)
write_bytes: +0             (no disk writes)


### Main thread wchan sampled 5 times over 1.5s


do_epoll_wait
do_epoll_wait
do_epoll_wait
do_epoll_wait
do_epoll_wait


### FDs of interest


 5 × anon_inode:[timerfd]       <- Bun timers
 3 × anon_inode:[eventpoll]
 3 × anon_inode:[eventfd]
13 × socket                     <- MCP stdio pipes + keep-alive HTTPS to Anthropic
 6 × /dev/pts/4                 <- TTY (stdin/stdout/stderr)


### Interpretation

- **Not a syscall deadlock**: main thread is in `do_epoll_wait`, not in a blocking syscall.
- **Not a CPU-bound busy loop**: `nonvoluntary_ctxt_switches` is tiny (36), CPU is ~20%.
- **App IS actively writing the TTY at ~30 fps** (`syscw` ~32/s, `wchar` ~2.6 KB/s) with zero disk I/O → the modal is continuously redrawing.
- **Input is being read** (`syscr` ~111/s) but never produces visible state transitions.
- High voluntary context switch rate (~810/s) consistent with a frame loop yielding between every tick.

This is the signature of a React/Ink render loop that keeps re-scheduling re-renders within every frame (state update inside effect/render → new rendernew tick), starving the input handler. Under `--debug`, synchronous `console.log` / `process.stderr.write` calls yield the main thread at different points, which breaks whatever microtask ordering is required to trigger the loop — a textbook Heisenbug.

---

claude mcp add bitbucket -s local \
     -e BITBUCKET_USERNAME=... \
     -e BITBUCKET_APP_PASSWORD=... \
     -e BITBUCKET_WORKSPACE=... \
     -- npx -y @aashari/mcp-server-atlassian-bitbucket

---

cd <any project dir>
   claude          # or: claude --resume
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

On Ubuntu 24.04 / WSL2 with Claude Code 2.1.119 (native install, Bun runtime), opening the /mcp modal hard-freezes the TUI: Enter, Esc, arrow keys, and Ctrl-C all become unresponsive. SIGINT is not processed. The only recovery is Ctrl-Z + kill -9 <pid>.

Critical clue: the bug does NOT reproduce when Claude Code is started with --debug. Same user, same config, same terminal, same session — /mcp works normally under --debug and freezes hard without it. This points to a timing-sensitive render loop where synchronous log output (only present in --debug) inadvertently breaks a bad state-update cycle.

Kernel-side inspection of a frozen process confirms it is not deadlocked on I/O and not in a CPU busy loop — the main thread repeatedly returns to epoll_wait while the process writes the TTY at ~30 Hz. Classic signature of a React/Ink render loop that re-schedules itself every frame, starving keyboard input processing.

Environment:

  • Claude Code 2.1.119 (latest on npm as of this report)
  • Install method: native (~/.local/share/claude/versions/2.1.119)
  • Runtime: Bun (confirmed via thread names: Bun Pool 0-7, HeapHelper, JITWorker)
  • Ubuntu 24.04 on WSL2, kernel 6.6.87.2-microsoft-standard-WSL2
  • Windows Terminal
  • Only ONE MCP server configured (npx -y @aashari/mcp-server-atlassian-bitbucket, stdio, status connected)
  • ENABLE_CLAUDEAI_MCP_SERVERS=false is set, so no cloud connectors in the list

Not a duplicate of #27367 (2-3s lag, different mechanism), #4232 (slash command latency), #23016 (Windows 11 startup freeze), or #19988 (freeze on large MCP responses). This is specifically about the /mcp modal hard-freezing with a single server, only when --debug is off.

What Should Happen?

Opening /mcp should render the server list and immediately respond to keyboard input (Enter to select, Esc to close, Ctrl-C to abort), regardless of whether Claude Code was started with --debug. Behavior must not depend on --debug being enabled.

Error Messages/Logs

No error is printed: the TUI renders the menu successfully and then silently becomes unresponsive. Because `--debug` is off when the bug reproduces, there are no debug logs to attach — this is part of what makes the bug hard to diagnose from user logs.

Instead, here is a **kernel-side snapshot** captured on a live frozen process (PID 1376, `claude --resume`, 35s elapsed, stuck on `/mcp`):

### Threads


$ ps -T -p 1376 -o tid,stat,%cpu,wchan:20,comm
  TID STAT %CPU WCHAN                COMMAND
 1376 Sl+  10.8 do_epoll_wait        claude         <- main thread
 1377 Sl+   0.4 futex_wait_queue     claude
 1378 Sl+   1.0 futex_wait_queue     HeapHelper
 1379 Sl+   1.0 futex_wait_queue     HeapHelper
 1380 Sl+   0.9 futex_wait_queue     HeapHelper
 1381 Sl+   0.9 futex_wait_queue     HeapHelper
 1382 Sl+   0.9 futex_wait_queue     HeapHelper
 1383 Sl+   0.9 futex_wait_queue     HeapHelper
 1384 Sl+   0.9 futex_wait_queue     HeapHelper
 1385 Sl+   2.2 futex_wait_queue     JITWorker
 1386-1399 Sl+ ~0 futex_wait_queue    Bun Pool 0-7
 1397 Sl+   0.0 do_epoll_wait        HTTP Client
 1445 Sl+   0.0 wait_woken           File Watcher


### Process state


$ cat /proc/1376/status | grep -E 'State|Threads|ctxt'
State: S (sleeping)
Threads: 20
voluntary_ctxt_switches:    28428    <- in ~35s of uptime = ~810/s
nonvoluntary_ctxt_switches: 36


### I/O activity (delta over 3 seconds)


rchar:       +11306 bytes   (~3.7 KB/s reads from TTY/sockets)
wchar:       +7759  bytes   (~2.6 KB/s writes to TTY)
syscr:       +333           (~111 read syscalls/s)
syscw:       +97            (~32 write syscalls/s)   <- TTY redraw at ~30 fps
read_bytes:  +0             (no disk reads)
write_bytes: +0             (no disk writes)


### Main thread wchan sampled 5 times over 1.5s


do_epoll_wait
do_epoll_wait
do_epoll_wait
do_epoll_wait
do_epoll_wait


### FDs of interest


 5 × anon_inode:[timerfd]       <- Bun timers
 3 × anon_inode:[eventpoll]
 3 × anon_inode:[eventfd]
13 × socket                     <- MCP stdio pipes + keep-alive HTTPS to Anthropic
 6 × /dev/pts/4                 <- TTY (stdin/stdout/stderr)


### Interpretation

- **Not a syscall deadlock**: main thread is in `do_epoll_wait`, not in a blocking syscall.
- **Not a CPU-bound busy loop**: `nonvoluntary_ctxt_switches` is tiny (36), CPU is ~20%.
- **App IS actively writing the TTY at ~30 fps** (`syscw` ~32/s, `wchar` ~2.6 KB/s) with zero disk I/O → the modal is continuously redrawing.
- **Input is being read** (`syscr` ~111/s) but never produces visible state transitions.
- High voluntary context switch rate (~810/s) consistent with a frame loop yielding between every tick.

This is the signature of a React/Ink render loop that keeps re-scheduling re-renders within every frame (state update inside effect/render → new render → new tick), starving the input handler. Under `--debug`, synchronous `console.log` / `process.stderr.write` calls yield the main thread at different points, which breaks whatever microtask ordering is required to trigger the loop — a textbook Heisenbug.

Steps to Reproduce

  1. On Ubuntu 24.04 WSL2, install Claude Code 2.1.119 (native install).
  2. In ~/.claude/settings.json set "env": { "ENABLE_CLAUDEAI_MCP_SERVERS": "false" } (optional, just to reduce the server list to one).
  3. Configure at least one MCP server. Example (stdio, npm-based):
    claude mcp add bitbucket -s local \
      -e BITBUCKET_USERNAME=... \
      -e BITBUCKET_APP_PASSWORD=... \
      -e BITBUCKET_WORKSPACE=... \
      -- npx -y @aashari/mcp-server-atlassian-bitbucket
  4. Start Claude Code without --debug:
    cd <any project dir>
    claude          # or: claude --resume
  5. At the prompt, type /mcp and press Enter.
  6. The modal renders showing bitbucket · ✔ connected.
  7. Press Enter, Esc, or Ctrl-C — nothing responds. The only exit is Ctrl-Z followed by kill -9 <pid>.

Control (does NOT reproduce):

  • Start with claude --debug instead. Same config, same terminal, same session — /mcp works normally, navigation and Esc respond immediately.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.119

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

No response

extent analysis

TL;DR

The issue can likely be fixed by modifying the render loop in the React/Ink code to avoid starving the input handler, potentially by introducing a delay or yield point to allow for input processing.

Guidance

  • Investigate the React/Ink render loop code to identify where the state update is triggering a new render, causing the loop to re-schedule itself every frame.
  • Consider introducing a delay or yield point in the render loop to allow for input processing, such as using setTimeout or requestIdleCallback.
  • Review the code for any potential microtask ordering issues that may be contributing to the problem.
  • Test the fix by running Claude Code with the modified render loop and verifying that the /mcp modal responds to keyboard input as expected.

Example

// Example of introducing a delay in the render loop
import { useState, useEffect } from 'react';

function MyComponent() {
  const [state, setState] = useState({});

  useEffect(() => {
    // Introduce a delay to allow for input processing
    const timeoutId = setTimeout(() => {
      // Update state and trigger a new render
      setState({ ...state, updated: true });
    }, 10);

    return () => clearTimeout(timeoutId);
  }, [state]);

  return <div>{/* Render component */}</div>;
}

Notes

The fix may require a deeper understanding of the React/Ink render loop and the specific code responsible for the issue. Additionally, introducing a delay or yield point may have performance implications that need to be considered.

Recommendation

Apply a workaround by modifying the render loop to introduce a delay or yield point, allowing for input processing and preventing the loop from starving the input handler. This fix should be tested thoroughly to ensure it resolves the issue without introducing new problems.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] /mcp` modal freezes TUI hard on WSL2 — only reproducible without `--debug [2 comments, 3 participants]