nektos/act · error

Compare not implemented for types: left: %+v, right: %+v

Error message

Compare not implemented for types: left: %+v, right: %+v

What it means

Thrown by the expression interpreter when a comparison operator (<, >, <=, >=, ==) is applied to operand kinds the compare switch does not handle (e.g. arrays, objects/maps, structs, slices, channels, pointers). The interpreter coerces both operands to a common reflect.Kind before comparing, and only numbers, strings, bools, and Invalid are supported. Any other kind falls into the default branch and the error reports the offending reflect Kinds.

Source

Thrown at pkg/exprparser/interpreter.go:420

		return impl.compareNumber(float64(leftValue.Int()), float64(rightValue.Int()), kind)

	case reflect.Float64:
		if rightValue.Kind() == reflect.Int {
			return impl.compareNumber(leftValue.Float(), float64(rightValue.Int()), kind)
		}

		return impl.compareNumber(leftValue.Float(), rightValue.Float(), kind)

	case reflect.Invalid:
		if rightValue.Kind() == reflect.Invalid {
			return true, nil
		}

		// not possible situation - params are converted to the same type in code above
		return nil, fmt.Errorf("Compare params of Invalid type: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())

	default:
		return nil, fmt.Errorf("Compare not implemented for types: left: %+v, right: %+v", leftValue.Kind(), rightValue.Kind())
	}
}

func (impl *interperterImpl) coerceToNumber(value reflect.Value) reflect.Value {
	switch value.Kind() {
	case reflect.Invalid:
		return reflect.ValueOf(0)

	case reflect.Bool:
		switch value.Bool() {
		case true:
			return reflect.ValueOf(1)
		case false:
			return reflect.ValueOf(0)
		}

	case reflect.String:
		if value.String() == "" {

View on GitHub (pinned to 4f41128141)

Solutions

  1. Extract a scalar field before comparing: `${{ fromJSON(x).version < fromJSON(y).version }}` instead of comparing whole objects.
  2. Convert operands with supported functions such as toJSON/fromJSON/format/number so both sides become strings or numbers.
  3. Replace the raw comparison with a function that handles composites, e.g. contains(join(arr, ','), 'val') instead of arr < other.
  4. If you control the expression surface, pre-validate operand types in Go with reflect.ValueOf(...).Kind() before invoking the interpreter.

Example fix

# before
if: ${{ matrix.cfg < matrix.other }}
# after
if: ${{ matrix.cfg.name < matrix.other.name }}
Defensive patterns

Strategy: try-catch

Validate before calling

// before invoking the interpreter, verify both comparands are scalar
func isScalar(v interface{}) bool {
    switch reflect.ValueOf(v).Kind() {
    case reflect.Invalid, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16,
         reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16,
         reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
        return true
    }
    return false
}

Try / catch

result, err := interpreter.Evaluate(expr)
if err != nil {
    if strings.Contains(err.Error(), "Compare not implemented for types") {
        // log and fall back to a string-based comparison or skip the condition
    }
    return err
}

Prevention

When it happens

Trigger: Evaluating a workflow expression like `${{ fromJSON('{"a":1}') < fromJSON('{"a":2}') }}` or comparing a matrix value that resolved to a map/slice/array with a scalar or another object. Also triggered by comparing values whose dynamic types differ so the coercion lands on an unsupported kind (e.g. Struct vs String).

Common situations: Comparing JSON objects produced by fromJSON, comparing github event payloads (objects) instead of scalar fields, comparing two arrays from matrix generation, or comparing a null against a composite value in a way that skips the Invalid-Invalid early return.

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/9730c94a50182c96. Report an issue: GitHub.