Skip to content

Check

One command that runs every CI gate in order, from formatting and the directive linter to Rojo builds, strict type analysis and the Luau regression runners.

python tools/check.py is the command agents run after every change and the command CI runs on every push. It runs formatting, lint, the directive linter, catalog freshness, two Rojo builds, strict luau-lsp type analysis and every regression runner, and ends with CHECK PASSED or CHECK FAILED. The decision that defines it: CI calls this script and nothing else, so a local pass on the pinned toolchain is a CI pass. AGENTS.md makes a passing check the definition of done, and forbids weakening a check, deleting a test or adding a suppression to get there. The workflow around it is on Agent workflow.

Terminal window
python tools/check.py # every step
python tools/check.py --quick # inner loop: tools, format, lint, directives, catalog, types, core
python tools/check.py --fix # format src and templates with StyLua first, then check
python tools/check.py --only types,lint
python tools/check.py --list # print the steps and exit

Each step prints PASS name (1.2s) or FAIL name (1.2s). A failing step also prints the last 40 lines of its output, indented. Steps keep running after a failure, so one run shows every broken gate. The last line is CHECK PASSED (n steps) or CHECK FAILED: a, b, and the exit status is 1 when anything failed. --only with an unknown step name exits before running anything and points at --list.

Tools come from <repo>/.tools/bin (installed by tools/setup.py) or from PATH, and LUAU_BIN selects the Luau runtime. A step whose tool is missing fails at once with missing <tool>; run python tools/setup.py. Build outputs go to a temporary directory that is removed afterwards, never into the repository.

Step Quick What it runs Fails when
tools yes Looks up rojo, stylua, selene, luau-lsp and luau. Any is missing. The output names the fix.
format yes stylua --check src templates. With --fix, stylua src templates first. A file is not StyLua-formatted (tabs, double quotes, 130 columns, from stylua.toml).
lint yes selene src templates with the roblox+buffer_bitops standard library from selene.toml. Selene exits with a failure.
directives yes python tools/directives.py. Any CK rule reports a finding. See Directives.
catalog yes python tools/catalog.py --check. docs/agents/Catalog.md differs from what the module headers generate. Rerun python tools/catalog.py.
build no rojo build ChloeKernel.project.json to a temporary .rbxl. The project does not build.
runtime no rojo build runtime.project.json to a temporary .rbxmx, then tools/check_runtime.py on it. The build fails, or the model holds development code (see Releases and vendoring).
types yes rojo sourcemap, then luau-lsp analyze over src with the Roblox definitions. Any type or syntax error, or a warning missing from tools/types-baseline.txt.
core yes tools/regressions/core.py A core regression fails.
network no tools/regressions/network.py A networking regression fails.
gameplay no tools/regressions/gameplay.py A gameplay regression fails.
persistence no tools/regressions/persistence.py A persistence regression fails.
datasafety no tools/regressions/datasafety.py A data-loss regression fails.
presentation no tools/regressions/presentation.py A client presentation regression fails. The step prints SKIP if the file does not exist.
vendor no python tools/test_vendor.py The vendor sync tool’s own tests fail.
tooling no tools/test_directives.py, tools/test_scaffold.py, tools/test_engine.py The linter, scaffolder or engine lookup tests fail.

--quick runs the seven steps marked quick. It skips both builds, five of the six runners and the tool tests. It is the inner loop while you iterate.

The types step builds a sourcemap with Rojo, then runs:

Terminal window
luau-lsp analyze --definitions=<globalTypes.d.luau> --base-luaurc=.luaurc --sourcemap=<sourcemap.json> --no-strict-dm-types src

.luaurc sets strict mode and turns on every lint except LocalShadow. The definitions file comes from the repository root or .tools/globalTypes.d.luau. When neither exists, the step downloads it from the luau-lsp repository and saves it under .tools, so the first run needs network access.

Every diagnostic is parsed into path: Code: message with the line and column stripped. TypeError and SyntaxError always fail. Any other diagnostic fails unless that normalized form appears in tools/types-baseline.txt, so edits elsewhere in a file do not churn the baseline. A baseline entry that is no longer reported prints a note asking for its removal but does not fail. An analyzer crash with no parsable output fails. At 0.9.1 the baseline holds one entry, a TableOperations warning in Serde.luau about # on a table with string keys.

.github/workflows/ci.yml runs on pushes to main, on pull requests, and as a reusable workflow. Its one job:

  1. checks out the repository,
  2. installs the aftman.toml pins with ok-nick/[email protected],
  3. sets up Python 3.12,
  4. downloads luau-ubuntu.zip from the Luau 0.737 release and verifies its SHA-256 (c3f000d0bb190301b9548f50f82ae9e217a8b5ba7b3551b038dbf28cb2e5ed32) before putting it on PATH,
  5. runs python tools/check.py.

The release workflow calls this workflow first and builds nothing unless it passes. See Releases and vendoring.

Tool Pin Pinned in
Rojo 7.4.4 aftman.toml
StyLua 2.0.2 aftman.toml
Selene 0.28.0 aftman.toml
luau-lsp 1.68.0 aftman.toml
Luau runtime 0.737 .github/workflows/ci.yml (URL and SHA-256)

python tools/setup.py installs them into <repo>/.tools on Linux x86_64, macOS x86_64 or aarch64, and Windows x86_64:

  • With aftman on PATH, it runs aftman install. Otherwise it downloads each pinned GitHub release zip into .tools/bin.
  • It installs the Luau runtime pinned in ci.yml, verifying the SHA-256 on Linux as CI does.
  • It downloads .tools/globalTypes.d.luau for luau-lsp.
  • It runs Selene once on a probe file so Selene fetches its Roblox standard library now and the check stays offline later.
  • It adds /.tools/ to .gitignore.
  • It records installed versions in .tools/versions.json and skips pins that are already installed, without network access. --force reinstalls everything.

check.py puts .tools/bin on PATH for its own run. Add it to your shell’s PATH to run the tools directly.

The repository’s .claude/settings.json wires two hooks for Claude Code:

  • After every Edit, Write or MultiEdit of a .luau file inside the project, .claude/hooks/after-edit.py formats the file with StyLua and runs the directive linter on it. Findings exit with status 2, which hands them back to the agent to fix in the same turn.
  • At session start in Claude Code on the web (CLAUDE_CODE_REMOTE=true), .claude/hooks/session-start.sh runs tools/setup.py, puts .tools/bin on the session PATH and warms Selene’s standard library cache, building a native-certificate Selene with cargo when the session proxy blocks the release binary’s download.

ChloeKernel v0.9.1. Access to the framework is private and granted individually by Chloe. Games built with it credit CryptedChloes in their description.