chenhg5/cc-connect · error

piSession: marshal extension_ui_response: %w

Error message

piSession: marshal extension_ui_response: %w

What it means

RespondPermission in agent/pi/session.go fails when it cannot JSON-marshal the extension_ui_response payload it is about to send back to pi. Because the response struct is built from simple fields (id, behavior), a marshal failure almost always indicates a programming error such as an unsupported value type (e.g. a channel or func accidentally placed in the options map).

Source

Thrown at agent/pi/session.go:1192

	case "confirm":
		// extension_confirm goes through the regular permission flow, not the
		// AskUserQuestion flow, so the engine sends plain "allow"/"deny"
		// PermissionResults (no UpdatedInput.answers). Forward as the default
		// case: confirmed=Behavior=="allow", else confirmed=false (pgate's
		// ctx.ui.confirm resolves false for both "deny" and "no confirmation",
		// which is what we want).
		fallthrough
	default:
		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

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped json error to find the offending field/type
  2. Ensure every value in the response is a JSON-safe type (string, bool, number, map, slice)
  3. Add json tags to any custom struct fields
  4. Add a unit test covering RespondPermission payload marshalling

Example fix

// before
resp := map[string]any{"id": extID, "callback": result.Callback}
b, err := json.Marshal(resp)
// after
resp := map[string]any{"id": extID, "confirmed": result.Behavior == "allow"}
b, err := json.Marshal(resp) // only JSON-safe values
Defensive patterns

Strategy: validation

Validate before calling

if _, err := json.Marshal(resp); err != nil {
    slog.Error("permission response not serializable", "err", err)
}

Type guard

func jsonSafe(v any) bool {
    _, err := json.Marshal(v)
    return err == nil
}

Try / catch

if err := sess.RespondPermission(ctx, id, result); err != nil {
    if strings.Contains(err.Error(), "marshal") {
        slog.Error("bad permission payload", "err", err) // code bug, do not retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling RespondPermission on a pi RPC session where the constructed resp map/struct contains a value json.Marshal cannot encode; effectively only reachable via code changes adding non-serializable fields.

Common situations: A developer extended the permission-response payload with a non-JSON-encodable type; custom struct without json tags holding unsupported types.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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