semaphoreui/semaphore · error

cannot set value

Error message

cannot set value

What it means

setBasicType in services/project/backup_marshal.go:112 uses reflection (reflect.Value.Set) to write decoded backup JSON into a struct field. Before setting, it checks v.CanSet(); if the reflect.Value was not obtained addressably (e.g. from a non-addressable value or an unexported field), Set is impossible and the function returns "cannot set value". This protects against a reflect panic.

Solutions

  1. Pass a pointer to the destination struct to the backup Unmarshal function (Unmarshal(data, &target)).
  2. Ensure all fields with backup tags are exported (start with an uppercase letter).
  3. Remove the backup/db tag from unexported fields, or skip them like the marshaler does with tag "-".
  4. If calling setBasicType directly (tests), obtain the field via reflect.New(t).Elem() rather than reflect.ValueOf(structValue).

Example fix

// before
target := db.Project{}
Unmarshal(data, target) // fields not settable
// after
target := db.Project{}
Unmarshal(data, &target)
Defensive patterns

Strategy: type-guard

Validate before calling

rv := reflect.ValueOf(target)
if rv.Kind() != reflect.Ptr || rv.IsNil() { return errors.New("target must be a non-nil pointer") }
if !rv.Elem().CanSet() { return errors.New("target not settable") }

Type guard

func isSettable(v reflect.Value) bool { return v.IsValid() && v.CanSet() }

Try / catch

if err := project.Unmarshal(data, &target); err != nil {
    if strings.Contains(err.Error(), "cannot set value") {
        // destination was not addressable; fix call site to pass a pointer
    }
    return err
}

Prevention

When it happens

Trigger: unmarshalValueWithBackupTags reaches a basic (non-struct/slice/map) field whose reflect.Value is not addressable - typically when unmarshaling into a non-pointer value, a copy of a struct, or an unexported struct field reached via reflect.

Common situations: Calling Unmarshal with a struct (not &struct) argument; unmarshaling into an element retrieved from a map by value; adding an unexported field with a backup/db tag to a backup entity struct.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at services/project/backup_marshal.go:112

		for _, key := range v.MapKeys() {
			// Assuming the key is a string
			mapKey := fmt.Sprintf("%v", key.Interface())
			mapValue, err := marshalValue(v.MapIndex(key))
			if err != nil {
				return nil, err
			}
			result[mapKey] = mapValue
		}
		return result, nil
	}

	// Handle other types (int, string, etc.)
	return v.Interface(), nil
}

func setBasicType(data any, v reflect.Value) error {
	if !v.CanSet() {
		return fmt.Errorf("cannot set value")
	}

	switch v.Kind() {
	case reflect.Bool:
		b, ok := data.(bool)
		if !ok {
			return fmt.Errorf("expected bool for field, got %T", data)
		}
		v.SetBool(b)
	case reflect.String:
		s, ok := data.(string)
		if !ok {
			return fmt.Errorf("expected string for field, got %T", data)
		}
		v.SetString(s)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		n, ok := toFloat64(data)
		if !ok {

View on GitHub (pinned to 1774ccb71a)