{"record":{"id":"4db7bc3a603d45ff","repo":"Jguer/yay","slug":"callback-must-return-nil-or-table-got-s","errorCode":null,"errorMessage":"callback must return nil or table, got %s","messagePattern":"callback must return nil or table, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/settings/lua/autocmd.go","lineNumber":361,"sourceCode":"\ttbl.RawSetString(\"makedepends\", e.stringArray(srcinfo.MakeDepends))\n\ttbl.RawSetString(\"checkdepends\", e.stringArray(srcinfo.CheckDepends))\n\ttbl.RawSetString(\"optdepends\", e.stringArray(srcinfo.OptDepends))\n\ttbl.RawSetString(\"provides\", e.stringArray(srcinfo.Provides))\n\ttbl.RawSetString(\"conflicts\", e.stringArray(srcinfo.Conflicts))\n\ttbl.RawSetString(\"replaces\", e.stringArray(srcinfo.Replaces))\n\n\treturn tbl\n}\n\nfunc (e *Engine) parseUpgradeSelectResult(value glua.LValue, validExcludes mapset.Set[string]) (UpgradeSelectResult, error) {\n\tvar result UpgradeSelectResult\n\tif value == glua.LNil {\n\t\treturn result, nil\n\t}\n\n\ttbl, ok := value.(*glua.LTable)\n\tif !ok {\n\t\treturn result, fmt.Errorf(\"callback must return nil or table, got %s\", value.Type())\n\t}\n\n\tif excludeValue := tbl.RawGetString(\"exclude\"); excludeValue != glua.LNil {\n\t\texcludeTbl, ok := excludeValue.(*glua.LTable)\n\t\tif !ok {\n\t\t\treturn result, fmt.Errorf(\"exclude must be a table\")\n\t\t}\n\n\t\tvar parseErr error\n\t\texcludeTbl.ForEach(func(_ glua.LValue, val glua.LValue) {\n\t\t\tif parseErr != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tlname, ok := val.(glua.LString)\n\t\t\tif !ok {\n\t\t\t\tparseErr = fmt.Errorf(\"exclude entries must be strings\")\n\t\t\t\treturn","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/Jguer/yay/blob/328f4b4939fb35f38c2f7c7ce3b8638a839bafa5/pkg/settings/lua/autocmd.go#L343-L379","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Change the callback to return a table, e.g. { exclude = {...}, skip_menu = false }","Return nil if you do not want to influence the selection","Check the value.Type() printed in the message to see what was actually returned","Move side-effect-only logic to a different event (PreInstall/PostInstall) that ignores return values","Add a trailing `return` guard so every code path returns nil or a table"],"exampleFix":"-- before\nautocmd('UpgradeSelect', function(e) return true end)\n-- after\nautocmd('UpgradeSelect', function(e) return { skip_menu = false, exclude = {} } end)","handlingStrategy":"type-guard","validationCode":"local res = myUpgradeSelect(mockEvent)\nif res ~= nil and type(res) ~= 'table' then error('UpgradeSelect callback must return nil or a table') end","typeGuard":"local function isTableOrNil(v) return v == nil or type(v) == 'table' end","tryCatchPattern":null,"preventionTips":["Never return bare booleans/strings from UpgradeSelect hooks","Return {} instead of false to signal an empty result","Add a trailing `return nil` to functions with conditional returns"],"tags":["lua","autocmd","type-mismatch","return-value"],"backgroundTag":"type-mismatch","analyzedSha":"328f4b4939fb35f38c2f7c7ce3b8638a839bafa5","analyzedAt":"2026-09-07T16:45:12.608Z","contentChangedAt":"2026-09-07T16:45:12.608Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}