Moud

Finding bugs#

When a script fails, Moud stops that one script, says why in the Output panel and in the game log, and keeps the rest of the place running. This page is about reading those messages: what each part of an error means, the mistakes that come up most often and the words Moud uses for them, and the warnings that point at a bug before it turns into an error.

Reading an error#

Take a server script that speeds a player up as soon as they join:

luau
-- server
local function onJoined(player)
    local character = player.character
    local humanoid = character.humanoid
    humanoid.walkSpeed = 24
end

game.players.joined:connect(onJoined)

With autoSpawn off the player has no body yet when joined fires, so character is nil and line 4 fails. Output shows it like this:

server/main.luau:4: attempt to index nil with 'humanoid' (character is nil)
    4 |     local humanoid = character.humanoid
      |                      ^^^^^^^^^
  server/main.luau:4 in onJoined
  • The first line is the file, the line and what went wrong. Moud reads the line that failed to say which value was nil, here character, so you do not have to work out which of three dots it was.
  • Under it is the line itself, with carets under the part that failed.
  • Then the stack: every function that was running, innermost first, each with its file and line. A helper that is called from ten places fails in one of them, and the stack says which.

Each file and line in the stack is a link. Clicking it opens your code editor at that line (set it in File > Code Editor). Copy puts the whole error, stack included, on the clipboard, which is what to paste when you ask someone for help.

The fix here is to set the speed when the body exists, which is what spawned is for. It also runs again for every new body after a respawn, which joined would not:

luau
game.players.spawned:connect(function(player)
    local character = player.character :: Character
    character.humanoid.walkSpeed = 24
end)

The usual mistakes#

Most errors are one of a handful of mistakes, and Moud names each one the same way every time, close to how Roblox words it, so that what you learned there still reads right here.

Indexing nil. attempt to index nil with 'humanoid' (character is nil) means the value before the dot was nil. It was never found, it was misspelled when you looked it up, or it was destroyed.

Calling nil. attempt to call a nil value (scores.reset is nil) means you called something that does not exist. A misspelled function name gives this, and so does calling a method with a dot instead of a colon, or using something that only exists on the other side, like camera in a server script. When one line makes several calls and Moud cannot tell which failed, it names all of them.

A wrong argument to an engine function.

server/main.luau:31: bad argument #1 'name' to Instance:waitForChild (expected a string, got number)

The number counts the arguments you wrote, so for a method the first one after the colon is #1. The name in quotes is the parameter from the reference, which tells you which argument it was without counting commas.

A destroyed instance. PlayZone has been destroyed, it was World.Map.PlayZone means your code still holds an instance that is gone. The path is where it was when it was destroyed, because by now it has no parent to show. This usually comes from a handler that outlives the thing it watches; stop using it from its destroying signal, or check thing.parent first.

A property that does not exist.

server/main.luau:40: transparancy is not a valid member of Part "World.Map.PlayZone", did you mean 'transparency'?

Moud compares the name with every property, event, method and child of that instance and suggests the closest one. Properties are camelCase, so Transparency gets the same suggestion.

Waiting where it cannot. A handler connected to a signal, a metamethod or a callback the engine calls has to finish before the engine carries on, so it cannot wait:

server/main.luau:62: cannot wait here: this code was called by the engine and has to finish straight away, wrap the part that waits in task.spawn

Do what it says and move the waiting part into task.spawn(function() ... end).

The same error every frame#

An error inside game.stepped or renderStepped happens again on the next frame, and the one after. Output keeps one entry for it, with a count and the time it last happened:

21:30:02  server   x212, last at 21:30:09
server/main.luau:54: PlayZone has been destroyed, it was World.Map.PlayZone

The game log does the same: the error is written once with its stack, and after that one short line every five seconds says how many more times it happened. The log stays readable, and you still know the error has not stopped.

Warnings#

A warning is something that works but is probably a bug. It shows in yellow, with the line that caused it, and is written to the log once.

Infinite yield. waitForChild without a timeout waits forever. When it has waited 5 seconds, you get Infinite yield possible on World.Map:waitForChild("Door"), the same as Roblox. Either the name is wrong, the child is made somewhere you did not expect, or you meant to give up after a while:

luau
local door = map:waitForChild("Door", 10)
if not door then
    warn("this map has no door")
    return
end

Deprecated functions. wait, spawn and delay still run so code pasted from elsewhere works, and each place you call them warns once to use task.wait, task.spawn and task.delay.

Connections piling up. A signal with 500 handlers is almost always a connect inside a loop or inside another handler, adding a new handler every time and never disconnecting the old ones. Connect once, or keep the connection and call disconnect() when you are done with it.

Your own scripts can warn too. warn(...) works like print but shows as a warning, with the line it came from:

luau
if #spawns == 0 then
    warn("no spawn points in", map:getFullName())
end

The Output panel#

Window > Output lists prints, errors and warnings from every side of the game. Along the top:

  • All, Info, Warnings and Errors show one level at a time.
  • Client, Server and Studio turn each source on and off. Studio is the editor itself: plugins, saving and the editor's own messages.
  • The text field keeps only the lines that contain what you type.
  • Clear on play empties the panel each time you press Play, so what you see is from this run.
  • Copy copies every line the filters currently show.

While you play in Studio, errors from server scripts show in your Output with their stack, even when the server runs on another machine, such as when you work on a place with others.

When it is not your script#

Sometimes the engine itself breaks while running your line. The error then says the engine broke here, this is a moud bug and not your script, and has an Engine details section folded under it, with what the engine was doing in Java. You do not need to read it. Send report sends the error to the Moud team with a few details about your system, so it can be fixed. See Crash reports for what a report holds and what happens when the whole game stops.