fix: permission request ID mismatch, SSE duplicates, and subprocess cleanup (#137)

This commit is contained in:
João Salavisa
2026-02-01 01:16:54 +00:00
committed by GitHub
parent 122deb3b13
commit 9ddf131a05
11 changed files with 186 additions and 59 deletions
@@ -711,8 +711,14 @@ func (m *Manager) GetProcessInfo() map[string]interface{} {
// handlePermissionRequest handles permission requests from the agent
// It stores the pending request and waits for a response from the backend
func (m *Manager) handlePermissionRequest(ctx context.Context, req *adapter.PermissionRequest) (*adapter.PermissionResponse, error) {
// Generate a unique ID for this permission request
pendingID := fmt.Sprintf("%s-%s-%d", req.SessionID, req.ToolCallID, time.Now().UnixNano())
// Use the adapter-provided pending ID if available, otherwise generate one.
// This ensures the ID sent to the frontend matches the one used for response lookup.
// For OpenCode (per_xxx) and Claude Code (requestID), the adapter passes its ID
// so we use the same ID throughout the permission flow.
pendingID := req.PendingID
if pendingID == "" {
pendingID = fmt.Sprintf("%s-%s-%d", req.SessionID, req.ToolCallID, time.Now().UnixNano())
}
m.logger.Info("handling permission request",
zap.String("pending_id", pendingID),
@@ -747,7 +753,6 @@ func (m *Manager) handlePermissionRequest(ctx context.Context, req *adapter.Perm
}()
// Send notification to backend via updates channel
// We use a custom notification type that the backend will recognize
m.sendPermissionNotification(pending)
// Wait for response indefinitely - user may close and reopen the page
@@ -761,6 +766,8 @@ func (m *Manager) handlePermissionRequest(ctx context.Context, req *adapter.Perm
case <-ctx.Done():
m.logger.Warn("permission request context cancelled",
zap.String("pending_id", pendingID))
// Send cancellation notification so the backend can update the permission message status
m.sendPermissionCancelledNotification(pending)
return &adapter.PermissionResponse{Cancelled: true}, nil
}
}
@@ -831,6 +838,29 @@ func (m *Manager) sendPermissionNotification(pending *PendingPermission) {
}
}
// sendPermissionCancelledNotification sends a notification that a permission request was cancelled.
// This happens when the context is cancelled (e.g., agent completes or user stops the task)
// before the user responds to the permission request.
func (m *Manager) sendPermissionCancelledNotification(pending *PendingPermission) {
event := adapter.AgentEvent{
Type: adapter.EventTypePermissionCancelled,
SessionID: pending.Request.SessionID,
PendingID: pending.ID,
}
m.logger.Info("sending permission cancelled notification",
zap.String("pending_id", pending.ID),
zap.String("session_id", pending.Request.SessionID))
select {
case m.updatesCh <- event:
// Sent successfully
default:
m.logger.Warn("updates channel full, dropping permission cancelled notification",
zap.String("pending_id", pending.ID))
}
}
// RespondToPermission responds to a pending permission request
func (m *Manager) RespondToPermission(pendingID string, optionID string, cancelled bool) error {
m.permissionMu.RLock()
@@ -0,0 +1,27 @@
//go:build linux
package process
import (
"os/exec"
"syscall"
)
// setProcGroup configures the command to run in its own process group.
// This allows us to kill all child processes together.
// On Linux, we also set Pdeathsig to ensure the child is killed if the parent dies
// unexpectedly (SIGKILL, crash, etc.) without calling Stop().
func setProcGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pdeathsig: syscall.SIGTERM,
}
}
// killProcessGroup kills the entire process group for the given PID.
// Returns nil if successful, or an error if the kill failed.
func killProcessGroup(pid int) error {
// Kill the entire process group by using negative PID
return syscall.Kill(-pid, syscall.SIGKILL)
}
@@ -1,4 +1,4 @@
//go:build unix
//go:build unix && !linux
package process
@@ -9,6 +9,8 @@ import (
// setProcGroup configures the command to run in its own process group.
// This allows us to kill all child processes together.
// Note: Pdeathsig is Linux-specific and not available on macOS/BSD.
// On these platforms, orphan cleanup relies on explicit Stop() calls.
func setProcGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}