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.

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)

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.