@@ -201,73 +201,86 @@ func (s *permissionService) Request(ctx context.Context, opts CreatePermissionRe
201201 return true, nil
202202 }
203203
204− s.requestMu.Lock()
205− defer s.requestMu.Unlock()
206−
207− // tell the UI that a permission was requested
208− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
209− ToolCallID: opts.ToolCallID,
210− })
211−
212− s.autoApproveSessionsMu.RLock()
213− autoApprove := s.autoApproveSessions[opts.SessionID]
214− s.autoApproveSessionsMu.RUnlock()
215−
216− if autoApprove {
204+ // Set up the request under requestMu, then release it *before*
205+ // blocking on the human response. Holding requestMu across the wait
206+ // would serialize every permission prompt process-wide, so parallel
207+ // sub-agents each needing approval would queue behind one another.
208+ // Per-request resolution is scoped by the pendingRequests map (keyed
209+ // by request ID), so the wait needs no global lock.
210+ respCh, requestID, resolved, granted := func() (chan bool, string, bool, bool) {
211+ s.requestMu.Lock()
212+ defer s.requestMu.Unlock()
213+
214+ // tell the UI that a permission was requested
217215 s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
218216 ToolCallID: opts.ToolCallID,
219− Granted: true,
220217 })
221− return true, nil
222− }
223218
224− fileInfo, err := os.Stat(opts.Path)
225− dir := opts.Path
226− if err == nil {
227− if fileInfo.IsDir() {
228− dir = opts.Path
229− } else {
230− dir = filepath.Dir(opts.Path)
219+ s.autoApproveSessionsMu.RLock()
220+ autoApprove := s.autoApproveSessions[opts.SessionID]
221+ s.autoApproveSessionsMu.RUnlock()
222+
223+ if autoApprove {
224+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
225+ ToolCallID: opts.ToolCallID,
226+ Granted: true,
227+ })
228+ return nil, "", true, true
231229 }
232− }
233230
234− if dir == "." {
235− dir = s.workingDir
236− }
237− permission := PermissionRequest{
238− ID: uuid.New().String(),
239− Path: dir,
240− SessionID: opts.SessionID,
241− ToolCallID: opts.ToolCallID,
242− ToolName: opts.ToolName,
243− Description: opts.Description,
244− Action: opts.Action,
245− Params: opts.Params,
246− }
231+ fileInfo, err := os.Stat(opts.Path)
232+ dir := opts.Path
233+ if err == nil {
234+ if fileInfo.IsDir() {
235+ dir = opts.Path
236+ } else {
237+ dir = filepath.Dir(opts.Path)
238+ }
239+ }
247240
248− if _, ok := s.sessionPermissions.Get(PermissionKey{
249− SessionID: permission.SessionID,
250− ToolName: permission.ToolName,
251− Action: permission.Action,
252− Path: permission.Path,
253− }); ok {
254− s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
255− ToolCallID: opts.ToolCallID,
256− Granted: true,
257− })
258− return true, nil
259− }
241+ if dir == "." {
242+ dir = s.workingDir
243+ }
244+ permission := PermissionRequest{
245+ ID: uuid.New().String(),
246+ Path: dir,
247+ SessionID: opts.SessionID,
248+ ToolCallID: opts.ToolCallID,
249+ ToolName: opts.ToolName,
250+ Description: opts.Description,
251+ Action: opts.Action,
252+ Params: opts.Params,
253+ }
260254
261− s.activeRequestMu.Lock()
262− s.activeRequest = &permission
263− s.activeRequestMu.Unlock()
255+ if _, ok := s.sessionPermissions.Get(PermissionKey{
256+ SessionID: permission.SessionID,
257+ ToolName: permission.ToolName,
258+ Action: permission.Action,
259+ Path: permission.Path,
260+ }); ok {
261+ s.notificationBroker.Publish(pubsub.CreatedEvent, PermissionNotification{
262+ ToolCallID: opts.ToolCallID,
263+ Granted: true,
264+ })
265+ return nil, "", true, true
266+ }
264267
265− respCh := make(chan bool, 1)
266− s.pendingRequests.Set(permission.ID, respCh)
267− defer s.pendingRequests.Del(permission.ID)
268+ s.activeRequestMu.Lock()
269+ s.activeRequest = &permission
270+ s.activeRequestMu.Unlock()
268271
269− // Publish the request
270− s.Publish(pubsub.CreatedEvent, permission)
272+ respCh := make(chan bool, 1)
273+ s.pendingRequests.Set(permission.ID, respCh)
274+
275+ // Publish the request
276+ s.Publish(pubsub.CreatedEvent, permission)
277+ return respCh, permission.ID, false, false
278+ }()
279+
280+ if resolved {
281+ return granted, nil
282+ }
283+ defer s.pendingRequests.Del(requestID)
271284
272285 select {
273286 case <-ctx.Done():