semaphoreui/semaphore · error

unsupported kind

Error message

unsupported kind %v

What it means

setBasicType (services/project/backup_marshal.go:147) only supports Bool, String, Int*, Uint*, and Float* kinds. If the destination reflect.Value has any other kind (time.Time, custom struct kinds reached here, complex, interface, etc.), it returns "unsupported kind %v". Struct/slice/map kinds are handled earlier in unmarshalValueWithBackupTags, so reaching this default indicates an unhandled leaf type.

Solutions

  1. Check which struct field triggered it and either add a case for that reflect.Kind in setBasicType (e.g. special handling for time.Time via string parsing).
  2. Exclude the field from backup processing with a `backup:"-"` struct tag.
  3. Change the field to one of the supported basic types (bool, string, int/uint/float).
  4. Pre-process the data so the field is a struct/slice/map (handled by other code paths) rather than an unsupported leaf kind.

Example fix

// before
type Env struct {
    CreatedAt time.Time `backup:"created"`
}
// after
type Env struct {
    CreatedAt time.Time `backup:"-"` // excluded from backup unmarshal
}
Defensive patterns

Strategy: fallback

Validate before calling

v := reflect.ValueOf(target).Elem().FieldByName("CreatedAt")
switch v.Kind() {
case reflect.Bool, reflect.String, reflect.Int, reflect.Float64:
    // supported
default:
    return fmt.Errorf("field kind %v unsupported by backup unmarshaler", v.Kind())
}

Type guard

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

Try / catch

if err := project.Unmarshal(data, &target); err != nil {
    if strings.Contains(err.Error(), "unsupported kind") {
        return fmt.Errorf("backup unmarshaler does not handle this field type: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A backup-tagged field has a kind not covered by setBasicType's switch - e.g. a time.Time value stored directly as a non-struct handling path, a complex128 field, or a custom named type whose kind falls through. Also produced deliberately in Test_SetBasicType_InvalidType_ReturnsError.

Common situations: Adding a field of a new type (time.Time, json.RawMessage, custom scalar) to a backup entity struct without extending setBasicType; schema evolution adding fields the unmarshaler does not yet handle.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at services/project/backup_marshal.go:147

		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)
		}
		v.SetInt(int64(n))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)
		}
		v.SetUint(uint64(n))
	case reflect.Float32, reflect.Float64:
		n, ok := toFloat64(data)
		if !ok {
			return fmt.Errorf("expected number for field, got %T", data)
		}
		v.SetFloat(n)
	default:
		return fmt.Errorf("unsupported kind %v", v.Kind())
	}
	return nil
}

func toFloat64(data any) (float64, bool) {
	switch n := data.(type) {
	case float64:
		return n, true
	case float32:
		return float64(n), true
	case int:
		return float64(n), true
	case int64:
		return float64(n), true
	case int32:
		return float64(n), true
	case int16:
		return float64(n), true

View on GitHub (pinned to 1774ccb71a)