semaphoreui/semaphore · critical

cannot assign value of type

Error message

cannot assign value of type %s to field of type %s

What it means

setConfigValue panics when a scalar config value cannot be assigned to the target field's Go type via reflection: the cast result is neither assignable nor convertible to the field type. This happens for kinds outside String/Int/Bool that CastValueToKind does not handle (it returns the original value unchanged), or genuinely incompatible types.

Solutions

  1. Change the config value to match the field's expected type (string/int/bool as documented)
  2. Check the field's declared type in util.ConfigType and supply a compatible literal
  3. Cast the value before it reaches config loading (e.g. parse durations/ints yourself in pre-processing)
  4. Update to a version where the field type matches your value format

Example fix

// before
SLACK_NOTIFICATION_URL=12345   // field expects a URL string but cast path hits incompatible type
// after
SLACK_NOTIFICATION_URL=https://hooks.slack.com/services/T000/B000/XXXX
Defensive patterns

Strategy: validation

Validate before calling

field, ok := reflect.TypeOf(util.ConfigType{}).FieldByName("SomeField")
if ok && field.Type.Kind() == reflect.String {
    // value must be a plain string, not an int/bool/duration literal
}

Type guard

func kindSupported(v reflect.Value) bool {
    switch v.Kind() {
    case reflect.String, reflect.Int, reflect.Bool:
        return true
    }
    return false
}

Try / catch

func safeSet(attr reflect.Value, val string) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("type mismatch setting config: %v", r)
        }
    }()
    util.SetConfigValue(attr, val)
    return nil
}

Prevention

When it happens

Trigger: Assigning a string/env value to a config field of an unsupported kind (e.g. time.Duration, struct, custom type) so reflect cannot convert string to it; passing a bool-cast value where the field is a non-convertible type.

Common situations: Config fields whose types changed between versions; users putting arbitrary strings into fields typed as non-string kinds; env-var overrides applied via reflection at startup.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/5995587e61054094. Report an issue: GitHub.

Appendix: source

Thrown at util/config.go:1365

			}
			attribute.Set(reflect.ValueOf(arr))
		case reflect.Map:
			mapType := attribute.Type()
			mapValue := reflect.New(mapType)
			err := json.Unmarshal([]byte(value), mapValue.Interface())
			if err != nil {
				panic(err)
			}
			attribute.Set(mapValue.Elem())
		default:
			newValue, _ := CastValueToKind(value, kind)
			convertedValue := reflect.ValueOf(newValue)
			if convertedValue.Type().AssignableTo(attribute.Type()) {
				attribute.Set(convertedValue)
			} else if convertedValue.Type().ConvertibleTo(attribute.Type()) {
				attribute.Set(convertedValue.Convert(attribute.Type()))
			} else {
				panic(fmt.Errorf("cannot assign value of type %s to field of type %s", convertedValue.Type(), attribute.Type()))
			}
		}

	} else {
		panic(fmt.Errorf("got non-existent config attribute"))
	}
}

func getConfigValue(path string) string {
	attribute := reflect.ValueOf(Config)
	nested_path := strings.Split(path, ".")

	for i, nested := range nested_path {
		attribute = reflect.Indirect(attribute).FieldByName(nested)
		lastDepth := len(nested_path) == i+1
		if !lastDepth && attribute.Kind() != reflect.Struct && attribute.Kind() != reflect.Pointer ||
			lastDepth && attribute.Kind() == reflect.Invalid {
			panic(fmt.Errorf("got non-existent config attribute '%v'", path))

View on GitHub (pinned to 1774ccb71a)