Jguer/yay · error

callback must return nil or table, got

Error message

callback must return nil or table, got %s

What it means

parseUpgradeSelectResult requires the UpgradeSelect callback to return either nil or a table; any other Lua type (string, number, boolean, function) produces 'callback must return nil or table, got <type>'. This enforces the hook's contract so yay can safely read exclude/skip_menu fields.

Solutions

  1. Change the callback to return a table, e.g. { exclude = {...}, skip_menu = false }
  2. Return nil if you do not want to influence the selection
  3. Check the value.Type() printed in the message to see what was actually returned
  4. Move side-effect-only logic to a different event (PreInstall/PostInstall) that ignores return values
  5. Add a trailing `return` guard so every code path returns nil or a table

Example fix

-- before
autocmd('UpgradeSelect', function(e) return true end)
-- after
autocmd('UpgradeSelect', function(e) return { skip_menu = false, exclude = {} } end)
Defensive patterns

Strategy: type-guard

Validate before calling

local res = myUpgradeSelect(mockEvent)
if res ~= nil and type(res) ~= 'table' then error('UpgradeSelect callback must return nil or a table') end

Type guard

local function isTableOrNil(v) return v == nil or type(v) == 'table' end

Prevention

When it happens

Trigger: An UpgradeSelect autocmd callback whose final return statement yields a non-table, non-nil value — e.g. `return true`, `return 'skip'`, or a function call result.

Common situations: Users writing hooks that return a status string or boolean instead of an options table; copy-pasting callbacks from other events (like PreInstall, NRet=0) into UpgradeSelect; forgetting a return so an earlier expression result leaks.

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/4db7bc3a603d45ff. Report an issue: GitHub.

Appendix: source

Thrown at pkg/settings/lua/autocmd.go:361

	tbl.RawSetString("makedepends", e.stringArray(srcinfo.MakeDepends))
	tbl.RawSetString("checkdepends", e.stringArray(srcinfo.CheckDepends))
	tbl.RawSetString("optdepends", e.stringArray(srcinfo.OptDepends))
	tbl.RawSetString("provides", e.stringArray(srcinfo.Provides))
	tbl.RawSetString("conflicts", e.stringArray(srcinfo.Conflicts))
	tbl.RawSetString("replaces", e.stringArray(srcinfo.Replaces))

	return tbl
}

func (e *Engine) parseUpgradeSelectResult(value glua.LValue, validExcludes mapset.Set[string]) (UpgradeSelectResult, error) {
	var result UpgradeSelectResult
	if value == glua.LNil {
		return result, nil
	}

	tbl, ok := value.(*glua.LTable)
	if !ok {
		return result, fmt.Errorf("callback must return nil or table, got %s", value.Type())
	}

	if excludeValue := tbl.RawGetString("exclude"); excludeValue != glua.LNil {
		excludeTbl, ok := excludeValue.(*glua.LTable)
		if !ok {
			return result, fmt.Errorf("exclude must be a table")
		}

		var parseErr error
		excludeTbl.ForEach(func(_ glua.LValue, val glua.LValue) {
			if parseErr != nil {
				return
			}

			lname, ok := val.(glua.LString)
			if !ok {
				parseErr = fmt.Errorf("exclude entries must be strings")
				return

View on GitHub (pinned to 328f4b4939)