Safe Resource Cleanup
NPCs, blips, prompts, vehicles, and mission state that outlive the interaction they belonged to are the single most common source of "ghost" bugs in RedM resources. wc_libs' Flow helpers exist specifically to make cleanup a one-line call instead of a checklist you forget half of.
The cleanup bag pattern
CreateCleanupBag gives you a single object to register everything you spawn against, and a single Clean() call that tears it all down in the right order (prompts hidden and deleted, blips/GPS cleared, then peds/vehicles/objects deleted).
local bag = wc:CreateCleanupBag()
local npc = wc:SpawnPed(hash, x, y, z, heading, true, true)
bag:AddPed(npc)
local prompt = wc:CreatePrompt("Talk [G]")
bag:AddPrompt(prompt)
bag:AddBlip(wc:CreateMissionMarker(x, y, z, "Encounter", { route = true }))
bag:AddGPS()
-- when the interaction ends, whatever the reason:
bag:Clean()
Wire cleanup to every exit path
The bag itself is idempotent — calling Clean() twice is harmless — so wire it into every possible ending, not just the "happy path":
-
Success
clientwc:WatchPlayerNear(dest, 5.0, function() bag:Clean() TriggerServerEvent('myjob:complete') end) -
Timeout / cancel
clientwc:WatchPlayerNear(dest, 5.0, onArrive, { timeoutMs = 600000, onCancel = function(reason) bag:Clean() end, }) -
Resource restart
You don't need to handle this manually for watchers/progress bars — wc_libs auto-cancels anything tracked to your resource on
onResourceStop. But raw world entities (peds spawned outside a watcher's lifetime) still need an explicit stop handler:clientAddEventHandler('onResourceStop', function(resource) if resource == GetCurrentResourceName() then bag:Clean() end end)
Server-side state cleanup
Cleanup isn't just visual — server-tracked mission state (see the job guide's ActiveRuns table) needs the same treatment on disconnect, or you'll leak memory and can permanently block a player from starting a new run.
AddEventHandler('playerDropped', function()
ActiveRuns[source] = nil
ActiveDeliveries[source] = nil
end)
Vehicle cleanup
Use DeleteWagon or DeleteVehicle — never the raw DeleteVehicle native directly — since both unmark the mission-entity flag and unfreeze first, which the raw native won't do for you.
local wagon = wc:SpawnWagon('WAGON02X', x, y, z, heading, { isMission = true })
bag:AddVehicle(wagon) -- CleanupBag calls DeleteVehicle/DeletePed appropriately based on entity type
Common mistakes
- Only cleaning up on the success path and leaving blips/NPCs behind on timeout, cancellation, or disconnect.
- Deleting entities with the raw native instead of
wc:DeletePed/wc:DeleteVehicle/wc:DeleteWagon— the engine can silently refuse to delete a frozen or mission-flagged entity. - Forgetting server-side state cleanup entirely — visual cleanup only fixes what the player sees, not what the server still remembers about them.
- Creating a new cleanup bag per attempt without discarding the old one, leaking references across retries of the same interaction.