Jguer/yay · error

expected number, got

Error message

expected number, got %s

What it means

assign maps a Lua value onto a reflect.Int/Int64 struct field and requires an LNumber. Lua has a single numeric type, but anything that is not a number (string, bool, table, nil) is rejected with an error naming the actual type. The caller wraps it with the yay.opt.<key> prefix.

Solutions

  1. Remove quotes so the value is a Lua number: yay.opt.retries = 3
  2. Convert deliberately with tonumber(...): yay.opt.retries = tonumber(os.getenv("RETRIES"))
  3. If the field actually needs a duration/string, change the Go field type and tag instead

Example fix

// before (init.lua)
yay.opt.retries = "3"
// after
yay.opt.retries = 3
Defensive patterns

Strategy: validation

Validate before calling

-- init.lua
local n = tonumber(yay_opt_retries_raw)
assert(n ~= nil, "retries must be numeric")
yay.opt.retries = n

Try / catch

if err != nil && strings.Contains(err.Error(), "expected number") {
    fixNumberOptionInInitLua()
}

Prevention

When it happens

Trigger: An init.lua option bound to an int field is set to a quoted string or other non-number, e.g. yay.opt.retries = "3" or yay.opt.timeout = nil.

Common situations: Quoting numbers out of habit from config formats where everything is a string; Lua arithmetic returning nil due to a missing variable; hex or formatted strings ('0x10', '5s').

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 Jguer/yay@328f4b4939 (2026-09-07). Data as JSON: /api/errors/0d8bd0d16a9037b5. Report an issue: GitHub.

Appendix: source

Thrown at pkg/settings/lua/lua.go:177

	switch field.Kind() {
	case reflect.String:
		s, ok := val.(lua.LString)
		if !ok {
			return fmt.Errorf("expected string, got %s", val.Type())
		}

		field.SetString(string(s))
	case reflect.Bool:
		b, ok := val.(lua.LBool)
		if !ok {
			return fmt.Errorf("expected boolean, got %s", val.Type())
		}

		field.SetBool(bool(b))
	case reflect.Int, reflect.Int64:
		n, ok := val.(lua.LNumber)
		if !ok {
			return fmt.Errorf("expected number, got %s", val.Type())
		}

		field.SetInt(int64(n))
	case reflect.Slice:
		return assignStructSlice(field, val)
	default:
		return fmt.Errorf("unsupported field kind %s", field.Kind())
	}

	return nil
}

// assignStructSlice fills a []Struct field from a Lua table keyed by name, e.g.
//
//	{ ["core"] = { url = "..." }, ["extra"] = { url = "..." } }
//
// Each entry becomes one struct: the table key populates the element's
// lua:"name" field and the sub-table populates the remaining fields. Entries

View on GitHub (pinned to 328f4b4939)