temporalio/temporal · error

ConvertGradualChange can only be used with scalar types for…

Error message

ConvertGradualChange can only be used with scalar types for now

What it means

ConvertGradualChange converts a gradual-change dynamic config value to T, but the generic implementation only supports scalar types (and slices of them per the switch); any other generic instantiation hits the default branch and panics. The panic occurs at conversion time when the setting is consumed, typically during static initialization.

Solutions

  1. Instantiate ConvertGradualChange only with scalar types (bool, int variants, float, string)
  2. For non-scalar settings, subscribe to the raw GradualChange[T] value instead of converting
  3. Extend the switch in gradual_change.go to support the needed kind if the library owner approves

Example fix

// before
var v = ConvertGradualChange[MyStruct](setting)
// after
var v = ConvertGradualChange[bool](setting) // or use SubscribeGradualChange for complex types
Defensive patterns

Strategy: type-guard

Type guard

isScalar[T]()

Prevention

When it happens

Trigger: Calling ConvertGradualChange[T] (directly or via helpers like MatchingUseNewMatcher / MatchingEnableFairness wrappers) with a T that is not a scalar — e.g. a struct, map, or pointer type.

Common situations: A developer writes a new gradual-change config for a custom struct type assuming generics support it; refactoring moves an existing scalar call site to a struct-based type.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/7658e4135d2ec450. Report an issue: GitHub.

Appendix: source

Thrown at common/dynamicconfig/gradual_change.go:112

			if s, err := convertString(v); err == nil {
				var change GradualChange[T]
				reflect.ValueOf(&change.New).Elem().SetString(s)
				return change, nil
			}
			return changeConverter(v)
		}
	case reflect.TypeFor[time.Duration]():
		return func(v any) (GradualChange[T], error) {
			if d, err := convertDuration(v); err == nil {
				var change GradualChange[T]
				reflect.ValueOf(&change.New).Elem().SetInt(int64(d))
				return change, nil
			}
			return changeConverter(v)
		}
	default:
		// nolint:forbidigo // this will be triggered from a static initializer before it can be triggered from production code
		panic("ConvertGradualChange can only be used with scalar types for now")
	}
}

// SubscribeGradualChange is a helper that allows subscribing to a GradualChange[T] as if it
// was a T. It handles setting a timer for when the setting may change in the future.
func SubscribeGradualChange[T any](
	subscribable TypedSubscribable[GradualChange[T]],
	changeKey []byte,
	callback func(T),
	timeSource clock.TimeSource,
) (T, func()) {
	w := &gradualChangeSubscribeWrapper[T]{changeKey: changeKey, callback: callback, clock: timeSource}

	w.lock.Lock()
	w.change, w.cancelSub = subscribable(w.changeCallback)
	val, _ := w.reevalLocked()
	w.lock.Unlock()

View on GitHub (pinned to bde624efd1)