chenhg5/cc-connect · error

piSession: write extension_ui_response: %w

Error message

piSession: write extension_ui_response: %w

What it means

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.

Source

Thrown at agent/pi/session.go:1201

		resp = map[string]any{
			"type":      "extension_ui_response",
			"id":        extID,
			"confirmed": result.Behavior == "allow",
		}
	}

	b, err := json.Marshal(resp)
	if err != nil {
		return fmt.Errorf("piSession: marshal extension_ui_response: %w", err)
	}
	b = append(b, '\n')

	slog.Debug("piSession: sending extension_ui_response", "id", extID, "behavior", result.Behavior)
	s.rpcStdinMu.Lock()
	_, err = s.rpcStdin.Write(b)
	s.rpcStdinMu.Unlock()
	if err != nil {
		return fmt.Errorf("piSession: write extension_ui_response: %w", err)
	}

	return nil
}

// ── AgentSession interface ──────────────────────────────────

func (s *piSession) Events() <-chan core.Event {
	return s.events
}

func (s *piSession) CurrentSessionID() string {
	v, _ := s.sessionID.Load().(string)
	return v
}

func (s *piSession) Alive() bool {
	return s.alive.Load()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the wrapped cause (os.ErrClosed / EPIPE) to confirm the child is gone
  2. Recreate the session and re-issue the request — the pending permission is unrecoverable
  3. Ensure Close()/Stop() resolves pending permissions before closing stdin to avoid races
  4. Monitor pi process health (exit status) to detect crashes early

Example fix

// before
if err := sess.RespondPermission(ctx, id, result); err != nil {
    return err
}
// after
if err := sess.RespondPermission(ctx, id, result); err != nil {
    if errors.Is(err, os.ErrClosed) {
        slog.Warn("pi session gone, dropping permission response", "id", id)
        return nil
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !sess.Alive() {
    return errors.New("cannot respond: pi session closed")
}

Type guard

func isPipeClosed(err error) bool {
    return errors.Is(err, os.ErrClosed) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.EPIPE)
}

Try / catch

if err := sess.RespondPermission(ctx, id, result); err != nil {
    if isPipeClosed(err) {
        slog.Warn("pi gone, permission response dropped", "id", id)
        return nil // or recreate session and re-request
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/d099400dece8b7bf. Report an issue: GitHub.