- Tool rendering: Unified tool_use/tool_result cards with collapsible results - Special rendering for WebSearch, WebFetch, Task, Write tools - File upload support with drag & drop - Permission dialog for tool approvals - Status bar with session stats and permission mode toggle - SSH-only mode: Removed local container execution - Host switching disabled during active session with visual indicator - Directory browser: Browse remote directories via SSH - Recent directories dropdown with localStorage persistence - Follow-up messages during generation - Improved scroll behavior with "back to bottom" button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4.2 KiB
4.2 KiB
Plan Mode Bug Fix - Claude WebUI
Datum: 2025-12-16 Status: DEPLOYED
Das Problem
Beim Plan Mode Approval im WebUI kam ein API Error:
API Error: 400 {"type":"error","error":{"type":"invalid_request_error",
"message":"messages.98.content.0: unexpected tool_use_id found in tool_result blocks:
toolu_01R6yjX7gqduKgMAuYuPzMeG. Each tool_result block must have a corresponding
tool_use block in the previous message."}}
Ursache
- Wenn das WebUI ein Plan Approval sendet, muss die
tool_use_idmit einem vorherigentool_useBlock matchen - Bei Message History Compaction gehen die ursprünglichen
tool_useBlöcke verloren - Das WebUI sendete
tool_resultfür Permissions, aber die zugehörigentool_useIDs existierten nicht mehr
Die Lösung
1. Scope-Problem gefixt
setPermissionModeViaControl wurde aus startClaudeSession in den äußeren WebSocket-Scope verschoben:
Datei: backend/server.js (Zeile ~307)
// Helper to set permission mode via control protocol (needs claudeProcess)
const setPermissionModeViaControl = (mode) => {
if (!claudeProcess) return;
const modeRequestId = generateRequestId();
const modeRequest = {
type: 'control_request',
request_id: modeRequestId,
request: {
subtype: 'set_permission_mode',
mode: mode
}
};
console.log(`[${sessionId}] Auto-switching permission mode to: ${mode}`);
claudeProcess.stdin.write(JSON.stringify(modeRequest) + '\n');
pendingControlRequests.set(modeRequestId, { type: 'set_permission_mode', mode });
};
2. Permission Responses als control_response statt tool_result
Datei: backend/server.js (Zeile ~641)
// All permission responses (including ExitPlanMode/plan approval) use control_response
// This avoids tool_result issues when message history is compacted
const permResponse = {
type: 'control_response',
response: {
subtype: 'success',
request_id: data.requestId,
response: data.allow
? { behavior: 'allow', updated_input: data.updatedInput || null }
: { behavior: 'deny', message: data.message || 'User denied permission' }
}
};
3. Plan Mode State Tracking
Datei: backend/server.js (Zeile ~302)
let inPlanMode = false; // Track if we're in plan mode
Bei ExitPlanMode Permission Request (Zeile ~441):
// Track plan mode state for ExitPlanMode
if (isPlanApproval && !inPlanMode) {
savedPermissionMode = currentPermissionMode;
inPlanMode = true;
console.log(`[${sessionId}] Entering plan mode, saved mode: ${savedPermissionMode}`);
}
Deployment
Nach Code-Änderungen muss das Backend-Image neu gebaut werden:
cd /home/sumdex/projects/claude-web-ui
docker compose build backend
docker compose up -d backend
Wichtig: Der Backend-Code ist NICHT als Volume gemountet, sondern im Image gebaut!
Verifizieren dass der Fix deployed ist:
docker exec claude-webui-backend cat /app/server.js | grep "setPermissionModeViaControl"
Relevante Dateien
| Datei | Beschreibung |
|---|---|
backend/server.js |
Haupt-Backend, WebSocket Handler, Permission Handling |
frontend/src/hooks/useClaudeSession.js |
Frontend Hook für Claude Session |
frontend/src/components/PermissionDialog.jsx |
Permission UI Dialog |
docker-compose.yml |
Container Konfiguration |
Session-Dump
Falls weitere Analyse nötig:
/home/sumdex/projects/webui-workspace/session-dump-20251216-164148.jsonl
Technischer Hintergrund
Control Protocol vs Tool Results
- tool_result: Muss immer mit einem
tool_usein der vorherigen Nachricht matchen - control_response: Hat diese Einschränkung nicht, funktioniert unabhängig von Message History
Message Compaction
Claude Code kompaktiert die Message History um Context zu sparen. Dabei können tool_use Blöcke entfernt werden, was zu ID-Mismatches führt wenn später tool_result mit diesen IDs gesendet wird.
Test
- WebUI öffnen: http://100.105.142.13:3000
- Neue Session starten
- Eine Anfrage stellen die Plan Mode triggert (z.B. "Implementiere Feature X")
- Plan Mode Approval im UI bestätigen
- Es sollte KEIN
unexpected tool_use_idError mehr kommen