antonmedv/fx · error

unsupported node kind %d

Error message

unsupported node kind %d

What it means

ToValue converts an internal JSON Node into a goja (JavaScript) value. It switches on the node's Kind; when the kind is not one of the recognized kinds (Null, Bool, Number, String, Object, Array, Undefined), the node data is corrupted or came from a parser that produced an unknown kind, so it panics with 'unsupported node kind %d'. This is an internal invariant violation, not a user-input error.

Source

Thrown at internal/jsonx/to_value.go:113

			}
		}

		return vm.NewArray(arr...)

	case NaN:
		return vm.ToValue(math.NaN())

	case Infinity:
		if n.Value[0] == '-' {
			return vm.ToValue(math.Inf(-1))
		}
		return vm.ToValue(math.Inf(1))

	case Undefined:
		return goja.Undefined()

	}
	panic(fmt.Sprintf("unsupported node kind %d", n.Kind))
}

// maxSafeInt is 2^53 - 1, the largest integer JS can represent exactly.
const maxSafeInt = 1<<53 - 1

// minSafeInt is -(2^53 - 1).
const minSafeInt = -maxSafeInt

// ParseNumber parses a number from a string as int64 or *big.Int.
func ParseNumber(s string) (interface{}, bool) {
	bi := new(big.Int)
	if _, ok := bi.SetString(s, 10); !ok {
		return nil, false
	}

	// Quickly reject values whose bit-length exceeds 54 (i.e. >= 2^53).
	// big.Int.BitLen returns the length of the absolute value in bits.
	if bi.BitLen() <= 53 {

View on GitHub (pinned to 4f31cd3a0c)

Solutions

  1. Inspect the panic message's kind number and add a matching case to the switch in ToValue (internal/jsonx/to_value.go)
  2. Ensure every Node construction site assigns a valid Kind constant from the jsonx package
  3. Update the library version so the node producer and ToValue switch are from the same code version

Example fix

// before
case Undefined:
	return goja.Undefined()
}
panic(fmt.Sprintf("unsupported node kind %d", n.Kind))
// after
case Undefined:
	return goja.Undefined()
case MyNewKind:
	return vm.ToValue(n.String())
}
panic(fmt.Sprintf("unsupported node kind %d", n.Kind))
Defensive patterns

Strategy: type-guard

Validate before calling

// before calling ToValue
if n.Kind < Null || n.Kind > Undefined {
	return fmt.Errorf("node has invalid kind %d", n.Kind)
}

Type guard

func hasSupportedKind(n *jsonx.Node) bool {
	switch n.Kind {
	case jsonx.Null, jsonx.Bool, jsonx.Number, jsonx.String, jsonx.Object, jsonx.Array, jsonx.Undefined:
		return true
	}
	return false
}

Try / catch

// Go has no catch for panics in the same goroutine; wrap if embedding:
func safeToValue(n *jsonx.Node, vm *goja.Runtime) (v goja.Value, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("toValue: %v", r)
		}
	}()
	return n.ToValue(vm), nil
}

Prevention

When it happens

Trigger: Calling ToValue (directly or via KeysComplete) with a Node whose Kind field was never set, was set to a value outside the jsonx kind constants, or came from a third-party constructor that introduced a new kind not yet handled in the switch.

Common situations: Developers adding a new Node kind to the AST who forget to add a case in the ToValue switch; building Node values by hand with a zero-valued Kind; a version mismatch where a caller constructs nodes from another package version.

Related errors


AI-assisted analysis of antonmedv/fx@4f31cd3a0c (2026-09-02). Data as JSON: /api/errors/4751f9e5760c6f59. Report an issue: GitHub.