Bug: Web UI stuck on 'Thinking...' - Frontend sends wrong message type (v1.12.0)
Environment
- claudecodeui version: 1.12.0 (installed via git clone from main branch)
- Node.js version: v25.2.1
- OS: macOS Darwin 24.6.0
- Installation method:
git clone+npm install+npm run build - @anthropic-ai/claude-agent-sdk version: 0.1.29
Description
When using the web UI to send a message to Claude, the interface shows "Thinking..." indefinitely and never receives a response. The WebSocket connection is established successfully, but chat messages from the frontend are not being processed by the server.
Root Cause Analysis
After extensive first-principles debugging, I identified the issue:
1. Backend SDK Integration Works ✅
Direct SDK test from the claudecodeui directory succeeds:
import { query } from './node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs';
const q = query({
prompt: 'Say exactly: HELLO',
options: { model: 'haiku', maxTurns: 1 }
});
for await (const msg of q) {
console.log(msg.type); // system, assistant, result - all received correctly
}
2. WebSocket Connection Works ✅
Authentication and connection succeed:
[OK] WebSocket authenticated for user: terryli
[INFO] Chat WebSocket connected
3. Server Message Handler Expects claude-command ✅
In server/index.js:716, the handler checks for type: 'claude-command':
ws.on('message', async (message) => {
const data = JSON.parse(message);
if (data.type === 'claude-command') {
// This is the only path that calls queryClaudeSDK()
await queryClaudeSDK(data.command, data.options, ws);
} else if (data.type === 'cursor-command') {
// ...
}
// No else clause - unknown message types are silently ignored
});
4. Frontend Message Type Mismatch ❌
When I manually send type: 'claude-command' via WebSocket, everything works:
ws.send(JSON.stringify({
type: 'claude-command', // <-- This works
command: 'Say hello',
options: { cwd: '/path/to/project' }
}));
// Response received:
// [WS] session-created
// [WS] claude-response (system)
// [WS] claude-response (assistant)
// [WS] claude-response (result)
// [WS] claude-complete
But when using the actual web UI, no server-side logs appear for message processing - only projects_updated broadcast events. This indicates the frontend is sending a message type that doesn't match any handler case.
Steps to Reproduce
- Install claudecodeui v1.12.0 via git clone
- Run
npm installandnpm run build - Start server with
node server/index.js - Open http://localhost:3001 in browser
- Login and select a project
- Type any message and press Enter
- Observed: "Thinking..." spinner shows indefinitely
- Expected: Claude response appears
Workaround
None currently. The web UI is non-functional for sending messages.
Validation Script
I created a test script that confirms the backend works when the correct message format is used:
// test-websocket.mjs
import WebSocket from 'ws';
const loginResponse = await fetch('http://localhost:3001/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'USER', password: 'PASS' })
});
const { token } = await loginResponse.json();
const ws = new WebSocket(`ws://localhost:3001/ws?token=${token}`);
ws.on('open', () => {
ws.send(JSON.stringify({
type: 'claude-command', // Must be this exact type
command: 'Say hello',
options: { cwd: '/path/to/project' }
}));
});
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
console.log('Received:', msg.type);
if (msg.type === 'claude-complete') {
console.log('SUCCESS - Backend works!');
ws.close();
}
});
Suggested Fix
Investigate what message type the frontend is actually sending. Look in the React frontend code (likely in src/ or dist/) for WebSocket send calls and ensure they use type: 'claude-command' with the expected payload structure:
{
type: 'claude-command',
command: string, // The user's message
options: {
cwd: string, // Project path
sessionId?: string, // For resuming sessions
projectPath?: string,
// ... other options
}
}
Additional Context
- The
handleChatConnectionfunction inserver/index.jssilently ignores message types that don't match known handlers - Adding a catch-all
elseclause with logging would help debug similar issues:} else { console.log('[WARN] Unknown message type:', data.type, data); }
I also encounter this problem. it's always stuck on "Thinking...". If I switch to shell i found below situation: