Files
claude-web-ui/PLAN-MODE-BUG-FIX.md
Nikolas Syring 960f2e137d feat: Major UI improvements and SSH-only mode
- 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>
2025-12-17 10:33:25 +01:00

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_id mit einem vorherigen tool_use Block matchen
  • Bei Message History Compaction gehen die ursprünglichen tool_use Blöcke verloren
  • Das WebUI sendete tool_result für Permissions, aber die zugehörigen tool_use IDs 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_use in 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

  1. WebUI öffnen: http://100.105.142.13:3000
  2. Neue Session starten
  3. Eine Anfrage stellen die Plan Mode triggert (z.B. "Implementiere Feature X")
  4. Plan Mode Approval im UI bestätigen
  5. Es sollte KEIN unexpected tool_use_id Error mehr kommen