story-kit: create 91_bug_permissions_dialog_never_triggers_in_web_ui
This commit is contained in:
@@ -6,27 +6,68 @@ name: "Permissions dialog never triggers in web UI"
|
|||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
The web UI permissions dialog has never successfully triggered. Claude Code in `-p` (pipe) mode is missing two critical CLI flags: `--input-format stream-json` (so it reads permission responses from stdin) and `--permission-mode default` (so it actually emits permission_request events instead of auto-approving). Without these, the full permission channel wiring (PTY → WebSocket → React dialog → WebSocket → PTY) is never exercised.
|
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
|
## How to Reproduce
|
||||||
|
|
||||||
1. Start the story-kit server
|
1. Start the story-kit server
|
||||||
2. Open the web UI and select claude-code-pty as the model
|
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. "run `ls` in the terminal")
|
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 executes automatically
|
4. Observe that no permission dialog appears — the tool either auto-approves or the denial appears as text in the tool drawer
|
||||||
|
|
||||||
## Actual Result
|
## Actual Result
|
||||||
|
|
||||||
Claude Code auto-approves all tool use. No `permission_request` NDJSON event is emitted, so the frontend dialog never renders.
|
No permission dialog renders. Permissions are either auto-approved (via allow list) or denied silently as text output.
|
||||||
|
|
||||||
## Expected Result
|
## Expected Result
|
||||||
|
|
||||||
A permission dialog should appear in the web UI showing the tool name and input, with Approve/Deny buttons. The user's response should be sent back to Claude Code via stdin.
|
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
|
## Acceptance Criteria
|
||||||
|
|
||||||
- [ ] Add --input-format stream-json to the claude CLI invocation in run_pty_session so Claude Code reads permission responses from stdin
|
- [ ] `prompt_permission` MCP tool exists and is callable by Claude Code via `--permission-prompt-tool`
|
||||||
- [ ] Add --permission-mode default to the claude CLI invocation so Claude Code emits permission_request events instead of auto-approving
|
- [ ] 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
|
- [ ] 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
|
- [ ] Approve a permission request and confirm Claude Code proceeds with the tool
|
||||||
- [ ] Deny a permission request and confirm Claude Code skips 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