wavetermdev/waveterm · error

Component function must return exactly 1 value

Error message

Component function must return exactly 1 value

What it means

The engine treats the single return value of a component function as the rendered component output, so validateCFunc requires rtype.NumOut() == 1. Functions returning zero values or multiple values (e.g. (any, error)) are rejected with "Component function must return exactly 1 value".

Solutions

  1. Make the function return exactly one value (the component output)
  2. If errors are possible, handle them inside the function and return an error component/value instead
  3. Wrap multi-return helpers in a single-return closure

Example fix

// before
root.RegisterComponent("stats", func(p map[string]any) (any, error) { ... })
// after
root.RegisterComponent("stats", func(p map[string]any) any {
    v, err := computeStats(p)
    if err != nil { return errorView(err) }
    return v
})
Defensive patterns

Strategy: validation

Validate before calling

t := reflect.TypeOf(cfunc)
if t != nil && t.Kind() == reflect.Func && t.NumOut() != 1 {
    return fmt.Errorf("need exactly 1 return value, got %d", t.NumOut())
}

Type guard

func isSingleReturnFunc(v any) bool {
    t := reflect.TypeOf(v)
    return t != nil && t.Kind() == reflect.Func && t.NumIn() == 1 && t.NumOut() == 1
}

Try / catch

if err := root.RegisterComponent(name, fn); err != nil {
    return fmt.Errorf("component %s must return exactly 1 value: %w", name, err)
}

Prevention

When it happens

Trigger: RegisterComponent with func(map[string]any) (any, error); registering a function that returns nothing (func(map[string]any) {}); registering a helper whose idiomatic Go signature is (T, error).

Common situations: Returning (result, error) out of Go habit; registering an existing function with a multi-value signature instead of wrapping it; registering a void procedure instead of a render function.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/074b504e35acd13a. Report an issue: GitHub.

Appendix: source

Thrown at tsunami/engine/rootelem.go:249

	defer r.atomLock.Unlock()

	delete(r.Atoms, name)
}

func validateCFunc(cfunc any) error {
	if cfunc == nil {
		return fmt.Errorf("Component function cannot b nil")
	}
	rval := reflect.ValueOf(cfunc)
	if rval.Kind() != reflect.Func {
		return fmt.Errorf("Component function must be a function")
	}
	rtype := rval.Type()
	if rtype.NumIn() != 1 {
		return fmt.Errorf("Component function must take exactly 1 argument")
	}
	if rtype.NumOut() != 1 {
		return fmt.Errorf("Component function must return exactly 1 value")
	}
	// first argument can be a map[string]any, or a struct, or ptr to struct (we'll reflect the value into it)
	arg1Type := rtype.In(0)
	if arg1Type.Kind() == reflect.Ptr {
		arg1Type = arg1Type.Elem()
	}
	if arg1Type.Kind() == reflect.Map {
		if arg1Type.Key().Kind() != reflect.String ||
			!(arg1Type.Elem().Kind() == reflect.Interface && arg1Type.Elem().NumMethod() == 0) {
			return fmt.Errorf("Map argument must be map[string]any")
		}
	} else if arg1Type.Kind() != reflect.Struct &&
		!(arg1Type.Kind() == reflect.Interface && arg1Type.NumMethod() == 0) {
		return fmt.Errorf("Component function argument must be map[string]any, struct, or any")
	}
	return nil
}

View on GitHub (pinned to a4447c1563)