---
name: "Permissions dialog never triggers in web UI"
---

# Bug 91: Permissions dialog never triggers in web UI

## Description

The web UI has a full permission dialog (Chat.tsx lines 848-947) that never triggers. The frontend and WebSocket client code are correct — the problem is entirely server-side. Claude Code in `-p` mode does not emit structured `permission_request` JSON events via stdout. Instead, when permissions are denied, it outputs plain text like "Claude requested permissions to use X, but you haven't granted it yet" in the tool drawer.

The correct mechanism for non-interactive permission handling is the `--permission-prompt-tool` CLI flag, which delegates permission decisions to an MCP tool. The server needs to expose a `prompt_permission` MCP tool that bridges the permission request through to the WebSocket client, where the frontend dialog can handle it.

## Investigation Findings (from spike work)

- `--permission-mode default` forces permission checks but denials come as **text output**, not structured JSON events
- `--input-format stream-json` is for multi-turn message streaming, not permission responses
- The PTY stdin/stdout approach for permission responses does not work in `-p` mode
- `--permission-prompt-tool <mcp_tool>` is the correct CLI mechanism — it calls the named MCP tool when a permission decision is needed, and the tool's return value (approve/deny) controls whether Claude Code proceeds
- The `.claude/settings.json` allow list auto-approves most tools, but `--permission-prompt-tool` overrides this

## How to Reproduce

1. Start the huskies server
2. Open the web UI and select claude-code-pty as the model
3. Send a message that triggers a tool requiring permission (e.g. "search the web for something")
4. Observe that no permission dialog appears — the tool either auto-approves or the denial appears as text in the tool drawer

## Actual Result

No permission dialog renders. Permissions are either auto-approved (via allow list) or denied silently as text output.

## Expected Result

A full-screen permission dialog appears showing the tool name and input, with Approve/Deny buttons. The user's response flows back to Claude Code, which proceeds or skips accordingly.

## Implementation Approach

### 1. Add `prompt_permission` MCP tool (`server/src/http/mcp.rs`)
- Tool accepts `tool_name` (string) and `tool_input` (object) parameters
- Handler sends the request through a shared channel to the active WebSocket session
- Handler blocks (async) waiting for the user's approve/deny response
- Returns a JSON result that Claude Code interprets as approval or denial

### 2. Add shared permission channel to AppContext (`server/src/http/context.rs`)
- Add a `PermissionForward` struct with request fields + a oneshot response sender
- Add an mpsc channel pair to `AppContext` for forwarding permission requests from MCP handler to WebSocket handler

### 3. Wire channel in server startup (`server/src/main.rs`)
- Create the permission channel pair and pass to AppContext

### 4. Bridge permissions in WebSocket handler (`server/src/http/ws.rs`)
- Replace the PTY-based `perm_req_tx`/`perm_req_rx` flow with the MCP-based shared channel
- On receiving a `PermissionForward` from the channel, send `WsResponse::PermissionRequest` to the client
- On receiving `WsRequest::PermissionResponse` from the client, send the decision back through the oneshot sender

### 5. Update Claude Code spawning (`server/src/llm/providers/claude_code.rs`)
- Add `--permission-prompt-tool mcp__huskies__prompt_permission` to the CLI args
- Remove `PermissionReqMsg` struct and `permission_tx` parameter (no longer needed — permissions flow through MCP, not PTY)
- Clean up the triple-duplicate `permission_request` match arms (dead code from story 80 merge)

### 6. Clean up chat function (`server/src/llm/chat.rs`)
- Remove `permission_tx` parameter from `chat()` signature (permissions no longer flow through here)

## Acceptance Criteria

- [ ] `prompt_permission` MCP tool exists and is callable by Claude Code via `--permission-prompt-tool`
- [ ] Permission requests flow: Claude Code → MCP tool → server channel → WebSocket → frontend dialog
- [ ] Permission responses flow: frontend dialog → WebSocket → server channel → MCP tool return → Claude Code
- [ ] Trigger a tool use via the web UI and confirm the permission dialog appears
- [ ] Approve a permission request and confirm Claude Code proceeds with the tool
- [ ] Deny a permission request and confirm Claude Code skips the tool
- [ ] Old PTY-based permission code (`PermissionReqMsg`, `permission_tx`, triple duplicate handlers) is removed
- [ ] cargo clippy and cargo test pass
