Having an error "attempt to call a RBXScriptConnection value"

Hello! I made myself a module script to handle hooks, so everything runs in 1 function for each hook. After some time, I started running into problems regarding the module script randomly stopping to work without an error. After hours of debugging using pcalls and prints, I found out, I’m getting “attempt to call a RBXScriptConnection value”, from what it seems, I seem to get it mostly, when using the “Players” service, but it’s not only that.

The error happens in this function:

function Hooks:ConnectHooks(object, func)
	local success, errorMessage = pcall(game:GetService(object)[func]:Connect(function(...)
		for _, v in pairs(self._function_hooks[object][func]) do
			v(...) --Errors here, when running callbacks
		end
	end))
	
	if not success then
		warn(debug.traceback(errorMessage, 4))
	end
end

Error:
image

Example of one of the hooks, which errors me.
image

If anything else is needed, I can provide more.

It is because you are passing the RBXScriptConnection itself as the first param of the pcall which expects a function.

The following:

pcall(game:GetService(object)[func]:Connect(function(...)
	for _, v in pairs(self._function_hooks[object][func]) do
		v(...) --Errors here, when running callbacks
	end
end))

is equivalent to the following (but without the pcall):

game:GetService(object)[func]:Connect(function(...)
	for _, v in pairs(self._function_hooks[object][func]) do
		v(...) --Errors here, when running callbacks
	end
end)() -- note the extra parentheses

There is no need for a pcall here.

Well the pcall was to find what’s causing this problem, so I assume the problem is completely different…

The pcall wouldn’t have caught the error, the issue is either the pcall itself or because something in self._function_hooks[object][func] is not a function (is a connection)

Hm weird, all should be fine. The isValid function specifically checks for all this stuff, so it should be fine?

function Hooks:AddHook(object, func, id, callback)
	id = tostring(id)

	if not self:IsValidHook(object, func, id, callback) then
		pcall(error("[HOOKS] FAILED TO HOOK " .. object .. " WITH " .. func))
		return
	end

	self:CheckHooks(object, func)

	if object == "RunService" and id ~= "RunServiceHooks" then
		--pcall(error("[HOOKS] TRIED TO HOOK RUNSERVICE IN THE WRONG PLACE. PLEASE USE HookRunService() INSTEAD"))
		pcall(error(debug.traceback("[HOOKS] TRIED TO HOOK RUNSERVICE IN THE WRONG PLACE. PLEASE USE HookRunService() INSTEAD")))
		return
	end

	if self._function_hooks[object][func][id] then
		pcall(error("[HOOKS] HOOK ID ALREADY EXISTS: " .. id))
		return
	end

	self._function_hooks[object][func][id] = callback

	return id
end
local function isValid(object, method, id, callback)
	local function isntEmpty(str)
		return str ~= ""
	end

	if object and method and id and callback and (type(callback) == "function") then --Split the if statements for better readability
		if isntEmpty(object) and isntEmpty(method) and isntEmpty(id) then
			return true
		end
	end
end

function Hooks:IsValidHook(object, func, id, callback)
	return (isValid(object, func, id, callback) and game:GetService(object) and game:GetService(object)[func]) or false
end

Where is line 147 of your script? That’s where the error is being outputted from as per your original post, I just want to make sure I’m looking in the right place

At the time, line 147 was the Hooks:ConnectHook func, when calling the callbacks

function Hooks:ConnectHooks(object, func)
	game:GetService(object)[func]:Connect(function(...)
		for _, v in pairs(self._function_hooks[object][func]) do
			v(...) --This was line 147 at the time
		end
	end)
end

Saying at the time, because I changed stuff like removing the pcalls.

Ok, that’s very odd because that shouldn’t have caused a warning but an error. Could you try printing typeof(v) and see if it sometimes gives you a function and other times gives you rbxscriptconnection?

Could you show me how you’re populating self._function_hooks[object][func]

So first, typeof(v) gives me all functions
image

And second, at this point I feel, like sending the whole script would be better.

If you could do that and send how you’re using it that might be the most efficient way to try figuring out what’s going on here

Edit: ok I got it, in your connect hooks function, I am not getting the warning with this:

function Hooks:ConnectHooks(object, func)
	game:GetService(object)[func]:Connect(function(...)
		for _, v in pairs(self._function_hooks[object][func]) do
			v(...)
		end
	end)
end

is there something special you do to make the warn happen?

Accidentally edited my last message.

Here’s the module

--vojin154 copyright :D

local Hooks = {}

---------------------------------SERVICE HOOKS----------------------------------
Hooks._function_hooks = {}
Hooks._run_service_hooks = {}

function Hooks:AddHook(object, func, id, callback)
	id = tostring(id)

	if not self:IsValidHook(object, func, id, callback) then
		pcall(error("[HOOKS] FAILED TO HOOK " .. object .. " WITH " .. func))
		return
	end

	self:CheckHooks(object, func)

	if object == "RunService" and id ~= "RunServiceHooks" then
		--pcall(error("[HOOKS] TRIED TO HOOK RUNSERVICE IN THE WRONG PLACE. PLEASE USE HookRunService() INSTEAD"))
		pcall(error(debug.traceback("[HOOKS] TRIED TO HOOK RUNSERVICE IN THE WRONG PLACE. PLEASE USE HookRunService() INSTEAD")))
		return
	end

	if self._function_hooks[object][func][id] then
		pcall(error("[HOOKS] HOOK ID ALREADY EXISTS: " .. id))
		return
	end

	self._function_hooks[object][func][id] = callback

	return id
end

function Hooks:HookRunService(id, waitTime, callback)
	waitTime = waitTime or 0
	self._run_service_hooks[id] = {
		callback = callback,
		waitTime = waitTime,
		t = waitTime
	}

	return id
end

function Hooks:RemoveRunServiceHook(id)
	if self._run_service_hooks[id] then
		self._run_service_hooks[id] = nil
	end
end

function Hooks:RemoveHook(id)
	self:RemoveTableEntry(id, self._function_hooks)
end

---------------------------------EVENT HOOKS------------------------------------
Hooks._event_hooks = {}

function Hooks:HookEvent(object, event, id, callback)
	if (not object) or (not event) or (not object[event]) or (not id) or (id == "") or not callback or (type(callback) ~= "function") then
		pcall(error("[HOOKS] FAILED TO HOOK " .. object .. " WITH " .. event))
		return
	end

	self:CheckEvents(object, event)

	if self._event_hooks[object][event][id] then
		pcall(error("[HOOKS] HOOK ID ALREADY EXISTS: " .. id))
		return
	end

	self._event_hooks[object][event][id] = callback

	return id
end

function Hooks:RemoveEvent(id)
	self:RemoveTableEntry(id, self._event_hooks)
end

---------------------------------UTIL FUNCTIONS---------------------------------
function Hooks:GetHook(id)
	for _, object in pairs(self._function_hooks) do
		for _, func in pairs(object) do
			for i, v in pairs(func) do
				if i == id then
					return i, v
				end
			end
		end
	end
end

function Hooks:GetEvent(id)
	for _, object in pairs(self._event_hooks) do
		for _, event in pairs(object) do
			for i, v in pairs(event) do
				if i == id then
					return i, v
				end
			end
		end
	end
end

---------------------------------CORE FUNCTIONS---------------------------------
local function isValid(object, method, id, callback)
	local function isntEmpty(str)
		return str ~= ""
	end

	if object and method and id and callback and (type(callback) == "function") then --Split the if statements for better readability
		if isntEmpty(object) and isntEmpty(method) and isntEmpty(id) then
			return true
		end
	end
end

function Hooks:IsValidHook(object, func, id, callback)
	return (isValid(object, func, id, callback) and game:GetService(object) and game:GetService(object)[func]) or false
end

function Hooks:IsValidEvent(object, event, id, callback)
	return (isValid(object, event, id, callback) and object[event]) or false
end

function Hooks:RemoveTableEntry(id, hookType)
	for _, object in pairs(hookType) do
		for _, method in pairs(object) do
			for i, _ in pairs(method) do
				if i == id then
					method[i] = nil
				end
			end
		end
	end
end

function Hooks:ConnectHooks(object, func)
	game:GetService(object)[func]:Connect(function(...)
		for _, v in pairs(self._function_hooks[object][func]) do
			warn(typeof(v))
		end
	end)
end

function Hooks:ConnectEvents(object, event)
	object[event]:Connect(function(...)
		for _, v in pairs(self._event_hooks[object][event]) do
			v(object, ...)
		end
	end)
end

function Hooks:CheckHooks(object, func)
	if self._function_hooks[object] == nil then
		self._function_hooks[object] = {}
	end

	if self._function_hooks[object][func] then
		return
	end

	self._function_hooks[object][func] = {}

	self:ConnectHooks(object, func)
end

function Hooks:CheckEvents(object, event)
	if self._event_hooks[object] == nil then
		self._event_hooks[object] = {}
	end

	if self._event_hooks[object][event] then
		return
	end

	self._event_hooks[object][event] = {}

	Hooks:ConnectEvents(object, event)
end

function Hooks:ExecuteRunServiceHooks(dt)
	for _, v in pairs(self._run_service_hooks) do
		if v.t > 0 then
			v.t -= dt
		else
			v.callback(dt)
			v.t = v.waitTime
		end
	end
end

Hooks:AddHook("RunService", "Heartbeat", "RunServiceHooks", function(dt)
	Hooks:ExecuteRunServiceHooks(dt)
end)

return Hooks

And it’s used by using either AddHook or HookEvent, AddHook is used to hook services, while HookEvent is to hook stuff like Button.MouseButton1Click.

Here’s an example of AddHook:

HooksModule:AddHook("Players", "PlayerRemoving", script.Name .. "PlayerRemoving", function(player)
	local playerVoted = Votes._players_voted[player]
	if playerVoted then
		Votes._maps_voted[playerVoted] -= 1
	end
end)

And here’s HookEvent:

local HooksModule = require(game:GetService("ReplicatedStorage").Modules.Hooks)

local currentlyOpened
for _, v in pairs(script.Parent:GetChildren()) do
	if v:IsA("Folder") then
		HooksModule:HookEvent(v.Button, "MouseButton1Click", script.Name, function(o)
			if currentlyOpened and currentlyOpened ~= v.Frame then
				currentlyOpened.Visible = false
			end

			v.Frame.Visible = not v.Frame.Visible
			currentlyOpened = v.Frame --Doesn't matter, if it's closed or not. It will be force closed either way.
		end)
	end
end

Additionally there’s to hook RunService, but I didn’t include it as it pretty much goes to AddHook.

Not sure if this will tell anything, but sometimes I get this:


Which seems to point to ConnectHooks.

Ok got it, what are you doing that causes you to get the warning though? I can’t get the warning to occur. Or do you have multiple hooks and it could be more than 1 it’s stemming from?

Multiple. I have no idea where the problem comes from.

Ok, in that case, inside your AddHook function, add another check that checks the type that callback is, and ensures it’s a function, eg:

	if type(callback) ~= 'function' then
		error('Callback was not a function')
	end

	self._function_hooks[object][func][id] = callback

see if that helps determine where this is coming from

Still nothing. I’m starting to get confused, this feels so stupid.

Yeah I’m at a loss here, sorry

Well, I’m assuming it was throwing RBXScriptConnection error, because I was using pcalls or at least something like that, so most likely the issue is different. However due to Roblox not telling me what, I feel meh about this.

Are you able to temporarily remove the pcalls until a clear error is seen, to ensure that what you are trying to remedy is actually the error.

I have removed the pcalls long ago.