[BUG] Slash command dropdown causes multi-second UI freeze due to thread contention

Resolved 💬 3 comments Opened Jan 20, 2026 by TimonKai Closed Jan 24, 2026

[BUG] Slash command dropdown causes multi-second UI freeze due to thread contention

Summary

Opening the "/" slash command dropdown causes a 5-10 second UI freeze. strace analysis reveals this is caused by thread contention with busy-wait spinlocks rather than any external blocking call.

Environment

  • Platform: Linux (WSL2)
  • OS Version: Linux 6.6.87.2-microsoft-standard-WSL2
  • Claude Code Version: 2.1.12 (native installer)
  • Installation Method: Native (curl -fsSL https://claude.ai/install.sh | bash)

Steps to Reproduce

  1. Start Claude Code with multiple MCP servers configured (Snowflake, Firecrawl, dbt-mcp, Neon, Context7)
  2. Have ~30 skills loaded (project + legacy commands)
  3. Type / to open the slash command dropdown
  4. Observe 5-10 second freeze before dropdown appears

Root Cause Analysis (via strace)

Attached strace to the Claude Code process during the freeze:

sudo strace -f -tt -T -p <PID> 2>&1 | tee /tmp/claude-trace.log

Finding 1: sched_yield() Storm (Busy Waiting)

The trace shows hundreds of sched_yield() calls in rapid succession:

[pid 100122] 14:19:05.755672 <... sched_yield resumed>) = 0 <0.000146>
[pid 100121] 14:19:05.755711 sched_yield( <unfinished ...>
[pid 100120] 14:19:05.755737 futex(0x73c3bd014190, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 100119] 14:19:05.755764 <... futex resumed>) = 0 <0.000903>
[pid 100124] 14:19:05.755791 futex(0x73c3c2e96de4, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 100123] 14:19:05.755817 sched_yield( <unfinished ...>
[pid 100122] 14:19:05.755857 sched_yield( <unfinished ...>

Problem: This indicates a userspace spinlock implementation. Instead of blocking (sleeping) on a mutex, threads spin in a tight loop, burning CPU time just checking if a lock is free.

Finding 2: High Futex Contention (EAGAIN)

Multiple futex calls return EAGAIN:

[pid 100124] ... futex resumed>) = -1 EAGAIN (Resource temporarily unavailable) <0.000208>
[pid 100123] ... futex resumed>) = -1 EAGAIN (Resource temporarily unavailable) <0.000168>

Problem: Threads are fighting over locks so aggressively that the state changes faster than the kernel can manage the wait queue.

Finding 3: Thread Convoy Effect

Sequential wakeup pattern instead of parallel processing:

PID 71330 reads an event (read(8, ...))
PID 71330 wakes PID 100118
PID 100118 wakes PID 100119
PID 100119 wakes PID 100120...

Problem: Threads serialize instead of running in parallel, defeating the purpose of the thread pool.

Finding 4: Redundant clock_gettime Calls

[pid 71330] ... clock_gettime(CLOCK_MONOTONIC_RAW, ...) = 0
[pid 71330] ... clock_gettime(CLOCK_MONOTONIC_RAW, ...) = 0
[pid 71330] ... clock_gettime(CLOCK_MONOTONIC_RAW, ...) = 0

Problem: 3-4 consecutive clock_gettime calls with no other syscalls between them suggests over-aggressive timing checks.

Impact

  • UI completely unresponsive for 5-10 seconds
  • CPU usage spikes during freeze (threads spinning)
  • Affects productivity significantly when using slash commands frequently

Suggested Fix

Replace the yield-based spinlock pattern with proper blocking synchronization:

  1. Use pthread_mutex_lock/pthread_cond_wait instead of sched_yield() loops
  2. Or use Node.js's built-in async primitives that properly block

The current pattern:

// Current (bad): busy-wait spinlock
while (!lock_available) {
    sched_yield();  // Burns CPU
}

Should become:

// Better: proper blocking
pthread_mutex_lock(&mutex);
while (!condition) {
    pthread_cond_wait(&cond, &mutex);  // Sleeps until signaled
}
pthread_mutex_unlock(&mutex);

Workaround (Partial)

Setting UV_THREADPOOL_SIZE=1 may reduce contention by forcing single-threaded operation, but this is not a proper fix and may slow down other operations.

Full strace Log

The complete strace output is available upon request (several MB of data showing the pattern repeating).

Additional Context

  • Initially suspected the auto-updater was causing the freeze (based on debug logs showing AutoUpdaterWrapper activity)
  • strace analysis proved this was incorrect — the freeze is purely internal thread contention
  • The issue occurs consistently when opening the "/" dropdown with many skills/MCP tools loaded

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗