temporalio/temporal · error
Can't deep copy non-zero time.Time: %v
Error message
Can't deep copy non-zero time.Time: %v
What it means
The dynamicconfig deep-copy machinery (used to isolate config values before mapstructure decoding) can only deep-copy zero time.Time values, since time.Time contains unexported fields. Copying a non-zero time.Time is impossible with this reflection approach, so it panics. The nolint note says this is expected to trigger only from static initializers, never production traffic.
Source
Thrown at common/dynamicconfig/deepcopy.go:61
return deepCopyValue(v.Elem()).Addr()
case reflect.Slice:
if v.IsNil() {
return v
}
nv := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
for i := range v.Len() {
nv.Index(i).Set(deepCopyValue(v.Index(i)))
}
return nv
case reflect.Struct:
// Special case for time.Time: it has unexported fields so we can't copy it field by
// field, but we can copy zero values (which is all we need for default values).
if v.Type() == reflect.TypeFor[time.Time]() {
if v.Interface().(time.Time).IsZero() {
return reflect.ValueOf(time.Time{})
}
// nolint:forbidigo // this will be triggered from a static initializer before it can be triggered from production code
panic(fmt.Sprintf("Can't deep copy non-zero time.Time: %v", v.Interface()))
}
nv := reflect.New(v.Type()).Elem()
for i := range v.Type().NumField() {
nv.Field(i).Set(deepCopyValue(v.Field(i)))
}
return nv
case reflect.Interface, reflect.Func, reflect.Chan:
// only nil values of any other reference types allowed!
if v.IsNil() {
return v
}
fallthrough
default:
// nolint:forbidigo // this will be triggered from a static initializer before it can be triggered from production code
panic(fmt.Sprintf("Can't deep copy value of type %s: %v", v.Type(), v))
}
}
View on GitHub (pinned to bde624efd1)
Solutions
- Change the default value to the zero time.Time{} and populate the timestamp at read/usage time
- Use a different type (e.g. unix seconds/milliseconds int64 or a duration) for time-like config fields
- Set the field only after decoding the config, not in the static default
Example fix
// before
var cfg = NewDefaultSetting(..., Setting{Deadline: time.Now()})
// after
var cfg = NewDefaultSetting(..., Setting{Deadline: time.Time{}}) // set real value after decode Defensive patterns
Strategy: validation
Validate before calling
// ensure default structs contain only zero time.Time
Prevention
- Zero-value time.Time in defaults
- Use durations/epoch ints instead
When it happens
Trigger: Declaring a dynamic config setting whose default value (or struct field inside a default struct) contains a non-zero time.Time, evaluated during package init when deepCopyForMapstructure/deepCopyValue runs.
Common situations: A developer adds a default like `time.Now()` or a populated timestamp to a config struct used as a dynamicconfig default; a struct default gains a new time.Time field that is initialized non-zero.
Related errors
- Can't deep copy value of type %s: %v
- ConvertGradualChange can only be used with scalar types for
- dynamicconfig.New*Setting must only be called from static in
- duplicate registration of dynamic config key: %q
- unsupported error type: %v
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/8985115454b9fdb6.
Report an issue: GitHub.