story-kit: queue 91_bug_permissions_dialog_never_triggers_in_web_ui for QA
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
---
|
||||
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 story-kit 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__story-kit__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
|
||||
Reference in New Issue
Block a user