{"record":{"id":"d099400dece8b7bf","repo":"chenhg5/cc-connect","slug":"pisession-write-extension-ui-response-w","errorCode":null,"errorMessage":"piSession: write extension_ui_response: %w","messagePattern":"piSession: write extension_ui_response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":1201,"sourceCode":"\t\tresp = map[string]any{\n\t\t\t\"type\":      \"extension_ui_response\",\n\t\t\t\"id\":        extID,\n\t\t\t\"confirmed\": result.Behavior == \"allow\",\n\t\t}\n\t}\n\n\tb, err := json.Marshal(resp)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: marshal extension_ui_response: %w\", err)\n\t}\n\tb = append(b, '\\n')\n\n\tslog.Debug(\"piSession: sending extension_ui_response\", \"id\", extID, \"behavior\", result.Behavior)\n\ts.rpcStdinMu.Lock()\n\t_, err = s.rpcStdin.Write(b)\n\ts.rpcStdinMu.Unlock()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"piSession: write extension_ui_response: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// ── AgentSession interface ──────────────────────────────────\n\nfunc (s *piSession) Events() <-chan core.Event {\n\treturn s.events\n}\n\nfunc (s *piSession) CurrentSessionID() string {\n\tv, _ := s.sessionID.Load().(string)\n\treturn v\n}\n\nfunc (s *piSession) Alive() bool {\n\treturn s.alive.Load()","sourceCodeStart":1183,"sourceCodeEnd":1219,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L1183-L1219","documentation":"RespondPermission in agent/pi/session.go fails when writing the marshalled extension_ui_response line to the pi RPC child's stdin fails. Like error 320, this means the pipe to the pi process is closed or broken — typically because the child exited or stdin was already closed. The write is serialized through rpcStdinMu.","triggerScenarios":"Answering a permission request after the pi process has died or is shutting down; Close() racing with a pending permission response; child crash mid-permission-prompt.","commonSituations":"User takes too long to approve and the pi process was stopped in between; pi crashed while a tool permission prompt was pending; daemon shutdown closing sessions while responses are in flight.","solutions":["Check the wrapped cause (os.ErrClosed / EPIPE) to confirm the child is gone","Recreate the session and re-issue the request — the pending permission is unrecoverable","Ensure Close()/Stop() resolves pending permissions before closing stdin to avoid races","Monitor pi process health (exit status) to detect crashes early"],"exampleFix":"// before\nif err := sess.RespondPermission(ctx, id, result); err != nil {\n    return err\n}\n// after\nif err := sess.RespondPermission(ctx, id, result); err != nil {\n    if errors.Is(err, os.ErrClosed) {\n        slog.Warn(\"pi session gone, dropping permission response\", \"id\", id)\n        return nil\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"if !sess.Alive() {\n    return errors.New(\"cannot respond: pi session closed\")\n}","typeGuard":"func isPipeClosed(err error) bool {\n    return errors.Is(err, os.ErrClosed) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.EPIPE)\n}","tryCatchPattern":"if err := sess.RespondPermission(ctx, id, result); err != nil {\n    if isPipeClosed(err) {\n        slog.Warn(\"pi gone, permission response dropped\", \"id\", id)\n        return nil // or recreate session and re-request\n    }\n    return err\n}","preventionTips":["Answer permission prompts promptly; the process may be torn down otherwise","Make Close/Stop resolve pending permissions before closing stdin","Detect child exit and invalidate outstanding permission promises","Retry at most once with a fresh session"],"tags":["go","ipc","broken-pipe","permissions"],"backgroundTag":"broken-pipe","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}