Roblox Studio error

cannot resume dead coroutine

What cannot resume dead coroutine means in Roblox Studio, the three things that cause it with the code that triggers each, and the fix for each one.

Updated 23 October 20263 min readErrorLuau

In short

A coroutine runs its function once. When the function returns, or errors, the coroutine is dead and cannot be started again. This error means something resumed it after that: usually a coroutine.wrap function called a second time, or a thread you saved and resumed later. Make a new coroutine each time, or use task.spawn, which does that for you.

On this page
  1. What it means
  2. Causes and fixes
  3. Fix it with RoCode
  4. Find the line
  5. Questions
  6. Sources

What the message means

Output (example)
ServerScriptService.Timer:12: cannot resume dead coroutine

coroutine.status(co) tells you where a coroutine is: suspended (not started, or paused at a yield), running, normal or dead. Only a suspended one can be resumed.

With coroutine.resume the error is not thrown; it comes back as the second value: false, "cannot resume dead coroutine". With coroutine.wrap it is thrown, and the script stops.

Causes and fixes

Three things cause this error in practice. Each one below has the smallest code that triggers it, then the fix.

A coroutine.wrap function was called twice

coroutine.wrap makes one coroutine and gives you a function that resumes it. The first call runs it to the end. The second call tries to resume the same, now dead, coroutine.

Breaks Script in ServerScriptService named Timer
local announce = coroutine.wrap(function()
	print("Round starting")
end)

announce()
announce() -- cannot resume dead coroutine

If you want to run something in the background each time, use task.spawn with a normal function. It starts a fresh thread on every call.

Fixed Script in ServerScriptService named Timer
local function announce()
	print("Round starting")
end

task.spawn(announce)
task.spawn(announce)

The coroutine errored, then was resumed again

An error inside a coroutine kills it. A loop that keeps calling coroutine.resume on it then gets false, "cannot resume dead coroutine" every time, and the first, real error is easy to miss.

Breaks Script in ServerScriptService named Worker
local worker = coroutine.create(function()
	while true do
		local value = nil
		print(value + 1) -- the real error kills the coroutine
		coroutine.yield()
	end
end)

for _ = 1, 3 do
	local ok, err = coroutine.resume(worker)
	print(ok, err) -- after the first: false cannot resume dead coroutine
end

Check what coroutine.resume returns. Print the first error, which is the real bug, and stop resuming once coroutine.status says dead.

Fixed Script in ServerScriptService named Worker
local worker = coroutine.create(function()
	local count = 0
	while true do
		count += 1
		print("tick", count)
		coroutine.yield()
	end
end)

for _ = 1, 3 do
	if coroutine.status(worker) == "dead" then
		break
	end
	local ok, err = coroutine.resume(worker)
	if not ok then
		warn("[Worker] stopped:", err)
	end
end

A saved thread was resumed after it ended

Code that saves coroutine.running() to wake it later (a custom wait, a cutscene step) breaks when the thread finished in the meantime, or was resumed twice by two events.

Breaks Script in ServerScriptService named Door
local waiting = nil

local thread = coroutine.create(function()
	waiting = coroutine.running()
	coroutine.yield()
	print("door opened")
end)
coroutine.resume(thread)

local function onDoorOpened()
	coroutine.resume(waiting)
end

onDoorOpened()
print(coroutine.resume(waiting)) -- false cannot resume dead coroutine

Clear the saved thread as soon as you resume it, and check its status first. For one-off waits on events, event:Wait() is simpler than saving threads.

Fixed Script in ServerScriptService named Door
local waiting: thread? = nil

local thread = coroutine.create(function()
	waiting = coroutine.running()
	coroutine.yield()
	print("door opened")
end)
coroutine.resume(thread)

local function onDoorOpened()
	local t = waiting
	waiting = nil
	if t and coroutine.status(t) == "suspended" then
		coroutine.resume(t)
	end
end

onDoorOpened()
onDoorOpened() -- does nothing the second time

Fix it in your place

Paste the error, RoCode fixes it in your place

Copy the line from Output and paste it here. RoCode opens the script it names in the place you have open in Studio, works out which of the causes above it is, and edits the code there instead of handing you a snippet to paste.

For example: ServerScriptService.Timer:12: cannot resume dead coroutine

RoCode is an AI agent for Roblox Studio: it reads the scripts in the place you have open, finds the line and edits it there, and every change is an undo point. It runs on Windows or macOS with a desktop browser. Free to start, with free credits every day.

How to find the line that causes it

  1. Open Output (Window menu, or the Script tab), clear it with Ctrl+K (⌘K on Mac), and press Play.
  2. Find the first red line. It starts with the script's place in the Explorer and the line number, such as ServerScriptService.Shop:12:.
  3. Click the red line. Studio opens that script with the cursor on the failing line.
  4. Read the lines between Stack Begin and Stack End under it: they list the calls that led there, newest first, so you can see which function called the broken one.
  5. Still not sure which value is wrong? Add print(typeof(x), x) just above the line, or set a breakpoint on it and read the values in the Watch window.

Questions

Should I use coroutines or task.spawn in Roblox?

For running a function in the background, task.spawn (or task.defer) is simpler and starts a fresh thread each time. Coroutines are for when you need to pause and resume the same function yourself.

Why does it only fail the second time?

The first resume runs the coroutine to its end, which makes it dead. Only the next resume fails.

How do I see the real error behind it?

Read the second value coroutine.resume returns the first time it gives false. That is the error that killed the coroutine; every later message is only a knock-on.

Can RoCode fix it?

Yes. Paste the Output line into RoCode. It finds the coroutine in the script in your open place and rewrites it with task.spawn or the status checks it needs.

Sources and further reading

How the code was checked: every block marked Fixed passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026), and every block marked Breaks compiles and fails only when it runs. The message wording was checked by running the broken code with the Luau command line tool and against the message strings in Roblox Studio itself on 23 October 2026. A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Stop hunting errors line by line.

RoCode reads the scripts in your open place, fixes the error where it happens and tells you what it changed. Free every day, no card required.