Skip to content

Running scripts

Terminal window
luneblox run <path>

The path is treated as a Luau module path. A .luau or .lua extension is removed first, and a trailing init segment is removed too, so these all name the same script:

Terminal window
luneblox run tools/build
luneblox run tools/build.luau
luneblox run tools/build/init.luau

For the module path tools/build, these files are candidates:

  • tools/build.luau
  • tools/build.lua
  • tools/build/init.luau
  • tools/build/init.lua

Exactly one of them must exist. If two exist, such as build.luau next to a build/init.luau, the path is ambiguous and the run fails. A directory with no init file fails with The path is a directory without an init file.

A relative path is tried in five places, in this order. The first place that resolves wins.

Order Location
1 The path itself, relative to the current directory
2 lune/<path> in the current directory
3 .lune/<path> in the current directory
4 lune/<path> in your home directory
5 .lune/<path> in your home directory

So a file in the current directory shadows a script of the same name in a script folder, and a project script shadows a global one.

An absolute path is used as given. The script folders are never searched for it.

When nothing matches, the error names the first location:

Failed to resolve script at path /work/project/build (NotFound)

The lune and .lune folders in a project hold its scripts: build steps, code generation, release tasks. Run them from the project root, since the folders are looked up in the current directory and not in parent directories.

The same folders in your home directory hold global scripts, which are found from any directory:

~/.lune/
notes.luau
Terminal window
cd /anywhere
luneblox run notes

luneblox list prints both sets. For each place it reads lune, or .lune when lune does not exist, so keep the scripts of one place in one folder.

If the file starts with #!, that first line is skipped. Line numbers in error messages still match the file.

#!/usr/bin/env -S luneblox run
print("run me directly")

Everything after the script path is passed to the script. luneblox does not parse it, so flags reach the script as written:

Terminal window
luneblox run deploy --env staging -v
--!strict
local process = require("@lune/process")
for index, arg in process.args do
print(index, arg)
end
1 --env
2 staging
3 -v

process.args is a read-only table of strings. It does not include luneblox, run or the script path, nor flags written before the path, such as --coverage, which are luneblox’s own. Environment variables are in process.env. See process.

A script path of a single - reads the script from stdin until it closes, then runs it:

Terminal window
echo 'print(1 + 1)' | luneblox run -

Arguments still work:

Terminal window
luneblox run - one two < script.luau

A script run this way is named stdin in error messages. It has no location on disk, so it can require the @lune/* libraries but not other files.

Situation Exit code
The script called process.exit(code) code
The script called process.exit() with no code 0
A thread threw an error that nothing caught 1
Everything finished without an error 0
The script could not be found or read 1

An explicit process.exit code wins over the automatic one: a script that logged an uncaught error in a spawned task and then calls process.exit(0) exits with 0.

An uncaught error in any thread counts, not only in the main one. The error is printed to stderr, the other threads keep running, and the exit code becomes 1 when the script ends:

--!strict
local task = require("@lune/task")
task.spawn(function()
error("background failure")
end)
task.wait(1)
print("still running")
Terminal window
luneblox run example.luau; echo $?

The last line printed is 1.

Exit codes are one byte, 0 to 255.

luneblox with no command, or luneblox repl, starts an interactive session:

Lune v0.11.3
> print(1 + 1)
2
> for i = 1, 2 do
>> print(i)
>> end
1
2
  • The >> prompt means the statement is incomplete and the next line continues it.
  • All input runs in one runtime, and the @lune/* libraries can be required.
  • An error is printed and the session goes on.
  • Ctrl+C prints Interrupt: ^C again to exit; pressing it again exits. Ctrl+D exits at once.
  • Each line is saved to .luneblox_history in the state directory and is available with the up arrow in later sessions. See Directories.

The REPL needs a terminal. Do not start it from CI or a git hook.

luneblox run turns on Luau’s native code generation for the script. To run it interpreted, set LUNE_LUAU_JIT to 0, false or off:

Terminal window
LUNE_LUAU_JIT=0 luneblox run bench.luau

Only those three values, in lower case, turn it off. Any other value, or no value, leaves it on.

The variable is read by luneblox run only. The REPL and standalone executables do not read it and run with native code generation off.

Whatever the setting, a chunk loaded with luau.load is compiled to native code only when its codegenEnabled option is set. See luau.

--coverage writes which lines ran, and how often, to an LCOV file that coverage tools and editors read. It goes before the script path:

Terminal window
luneblox run --coverage tests
luneblox run --coverage=coverage/lcov.info tests

With no file named, the report is lcov.info in the current directory. A file in a directory that does not exist creates the directory.

The report covers the script, every module it requires, and every chunk it loads with luau.load, which is listed under its debugName. A file required more than once is one record. Paths inside the current directory are written relative to it.

The report is written when the script ends, also when it ends with an error, so a test run that fails still says what it reached. Chunks are compiled to count every statement, as luau --coverage does, so a run with coverage can be slower than one without.

Every way of running Luau here - run, the REPL, a standalone executable - sets Luau’s fast flags to the live Roblox client’s values first. LUNE_ROBLOX_FFLAGS=0 skips that. See Fast flags.