cilium/cilium · error

Both %T.Transform and .TransformMany cannot be set

Error message

Both %T.Transform and .TransformMany cannot be set

What it means

validate() rejects a ReflectorConfig that sets both Transform and TransformMany, since the reflector cannot know which mapping to apply to each watch event. Exactly one (or neither) must be provided for RegisterReflector to succeed.

Source

Thrown at pkg/k8s/statedb.go:373

func (cfg ReflectorConfig[Obj]) validate() error {
	if cfg.Name == "" {
		return fmt.Errorf("%T.Name cannot be empty", cfg)
	}
	if cfg.Table == nil {
		return fmt.Errorf("%T.Table cannot be nil", cfg)
	}
	if (cfg.ListerWatcher == nil) == (cfg.SharedListerWatcher == nil) {
		return fmt.Errorf("Exactly one of %[1]T.ListerWatcher and %[1]T.SharedListerWatcher must be set", cfg)
	}
	if cfg.BufferSize <= 0 {
		return fmt.Errorf("%T.BufferSize (%d) must be larger than zero", cfg, cfg.BufferSize)
	}
	if cfg.BufferWaitTime <= 0 {
		return fmt.Errorf("%T.BufferWaitTime (%d) must be larger than zero", cfg, cfg.BufferWaitTime)
	}
	if cfg.Transform != nil && cfg.TransformMany != nil {
		return fmt.Errorf("Both %T.Transform and .TransformMany cannot be set", cfg)
	}
	return nil
}

type k8sReflector[Obj any] struct {
	ReflectorConfig[Obj]

	log      *slog.Logger
	initDone func(statedb.WriteTxn)
	db       *statedb.DB
	table    statedb.RWTable[Obj]
	source   stream.Observable[CacheStoreEvent]
}

func (r *k8sReflector[Obj]) run(ctx context.Context, health cell.Health) error {
	if r.CRDSync != nil {
		// Wait for the CRD to be registered.
		health.OK("Waiting for CRD registration")

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Keep only TransformMany and set Transform to nil (or the reverse) before calling RegisterReflector
  2. Refactor config construction so a single transform strategy is selected via a flag/branch
  3. Centralize transform assignment in one helper to avoid double assignment

Example fix

// before
cfg := k8s.ReflectorConfig[Obj]{..., Transform: t, TransformMany: tm}
// after
cfg := k8s.ReflectorConfig[Obj]{..., TransformMany: tm} // Transform cleared
Defensive patterns

Strategy: validation

Validate before calling

if cfg.Transform != nil && cfg.TransformMany != nil {
    return fmt.Errorf("reflector %s: set either Transform or TransformMany, not both", cfg.Name)
}

Type guard

func hasSingleTransform[Obj statedb.Object](cfg k8s.ReflectorConfig[Obj]) bool {
    return !(cfg.Transform != nil && cfg.TransformMany != nil)
}

Try / catch

if err := RegisterReflector(db, cfg); err != nil {
    if strings.Contains(err.Error(), "Transform") {
        return fmt.Errorf("reflector %s: conflicting transform config: %w", cfg.Name, err)
    }
    return err
}

Prevention

When it happens

Trigger: RegisterReflector with a config where both Transform and TransformMany function fields are non-nil — typically after switching from per-object to per-batch transforms (or vice versa) without clearing the old field.

Common situations: Migrating to TransformMany for efficiency while leaving the legacy Transform assigned; a config builder that unconditionally sets Transform and additionally sets TransformMany for list handling.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/e62543a1b6106c36. Report an issue: GitHub.