How to stop busy flags locking a feature for good

A handler that sets a busy flag and then returns early or errors leaves the feature locked. How to check first and clear the flag on every path.

warningLuaudit check: locks-left-set

What it looks like

A handler sets a flag so only one thing happens at a time: a machine in use, a pending trade request for a player, a round starting, a purchase being processed. Some path through the handler then stops before the flag is cleared: an early return after the flag is set, a call that can error, or a yield that never resumes. The flag stays set and the feature stays locked.

Luau
inUse = true
if typeof(stake) ~= "number" then
	return
end

What an exploiter could do

They send one call that stops halfway, for example with an argument the handler rejects after setting the flag. A shared machine, event or minigame stays "in use" for the rest of the server, or another player is left with a request that never clears, so nobody can trade with them or challenge them until they rejoin.

How to fix it

  • Check every argument and condition before setting the flag.
  • Wrap the work between setting and clearing the flag in pcall, and clear the flag whatever the result.
  • Store when the flag was set and treat it as clear after a timeout, so a stuck flag can't last.
  • Clear flags in PlayerRemoving, and in failure events such as TeleportInitFailed.

Vulnerable example

Vulnerable
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local gamePassId = 123456789
local requested: { [Player]: Player } = {}

ReplicatedStorage.Remotes.RequestCarry.OnServerEvent:Connect(function(player: Player, target: unknown)
	if typeof(target) ~= "Instance" or not target:IsA("Player") or target == player or requested[target] then
		return
	end
	requested[target] = player
	local owns = MarketplaceService:UserOwnsGamePassAsync(player.UserId, gamePassId)
	if owns then
		ReplicatedStorage.Remotes.CarryRequested:FireClient(target, player)
	else
		requested[target] = nil
	end
end)

Players.PlayerRemoving:Connect(function(player)
	requested[player] = nil
end)

Fixed example

Fixed
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local gamePassId = 123456789
local requestTimeout = 30
local requested: { [Player]: { from: Player, at: number } } = {}

ReplicatedStorage.Remotes.RequestCarry.OnServerEvent:Connect(function(player: Player, target: unknown)
	if typeof(target) ~= "Instance" or not target:IsA("Player") or target == player then
		return
	end
	local current = requested[target]
	if current and os.clock() - current.at < requestTimeout then
		return
	end
	local ok, owns = pcall(MarketplaceService.UserOwnsGamePassAsync, MarketplaceService, player.UserId, gamePassId)
	if not ok or not owns or target.Parent ~= Players then
		return
	end
	requested[target] = { from = player, at = os.clock() }
	ReplicatedStorage.Remotes.CarryRequested:FireClient(target, player)
end)

Players.PlayerRemoving:Connect(function(player)
	requested[player] = nil
end)

How to spot this in your own scripts, and the cases that only look like it, are part of the Luaudit check your AI runs.