Every serious language has a way to ask "is this even parseable?" without running it. bash -n script.sh. node --check file.js. python -m py_compile. As of 1.17.0, BoxLang has one too.
The new check action command parses one or more source files and reports syntax errors without executing the code or compiling it to bytecode.
It works across .bx, .bxs, .bxm, .cfm, .cfc, and .cfs, and because it runs on the real BLAST and the actual ANTLR parsers, results are accurate for both BoxLang and CFML source. It is not a linter approximating the grammar. It is the compiler's own front end, stopped before code generation.
Using It
# One or more explicit files
boxlang check myapp.bx myComponent.cfc
# An entire directory, walked recursively
boxlang check --source ./src
# Machine-readable output
boxlang check --source ./src --format json
A valid file produces minimal output and exits 0:
$ boxlang check good.bxs
✅ good.bxs
───────────────────────────────
✅ 1 valid ❌ 0 invalid (1 files checked)
An invalid file reports the file, line, column, and message, and exits 1:
$ boxlang check bad.bxs
❌ bad.bxs
bad.bxs: Line: 1 Col: 3 - Unclosed parenthesis [(] on line 1
if ( true {
^
───────────────────────────────
✅ 0 valid ❌ 1 invalid (1 files checked)
That exit code is the whole integration story for humans and pipelines. Drop it in a pre-commit hook and a syntax error never reaches the branch. Put it at the top of CI and a typo fails the build in seconds instead of after a full test suite runs and dies on the first import.
The options are small on purpose:
--source <PATH>walks a directory recursively--format <text|json>switches between human and machine output-q, --quietsuppresses per-file success output, though failures are always reported- Exit
0for all valid, exit1for any syntax error or usage error
The Part That Matters for AI Agents
Here is the case that motivated putting this in a release post rather than a tooling changelog entry.
An LLM writing BoxLang has no reliable way to know whether what it just produced is real syntax. It will confidently emit a construct that does not parse, because a plausible-looking token sequence and a valid one are not the same thing, and nothing in the generation process distinguishes them. The model is not lying. It has no mechanism for checking.
Without a checker in the loop, that error surfaces at runtime: in a stack trace, possibly during a test run, possibly in production, possibly never during the agent's own working session at all. The agent reports success, the code is broken, and the failure lands on a human hours later.
boxlang check --format json closes that loop. The agent writes a file, runs the checker, and gets back a structured array of {file, valid, issues} records with line, column, and message per issue.
[ {
"file" : "/path/to/demo/bad.bxs",
"valid" : false,
"issues" : [ {
"message" : "Unclosed parenthesis [(] on line 1\nif ( true {\n ^",
"line" : 1,
"column" : 3
} ]
}, {
"file" : "/path/to/demo/good.bxs",
"valid" : true,
"issues" : [ ]
} ]
That is a machine-readable correction signal, which means an agent can repair its own hallucination before the code is ever executed. The loop is simple enough to wire into any harness:
- Generate the file
- Run
boxlang check --format jsonagainst it - Non-zero exit means parse the
issuesarray, feed line, column, and message back to the model, and regenerate - Empty
issuesmeans proceed to the next step
Two properties make this work in practice rather than just in principle.
It never executes the code. Running the checker against generated output is safe by construction. There is no risk that validating a hallucinated file also runs it, which matters a great deal when the thing that wrote the file cannot vouch for what is in it.
It never compiles to bytecode. It is fast enough to run on every single write without the agent's iteration loop slowing to a crawl. A checker that costs a full compile gets skipped, and a skipped checker is worth nothing.
One Mechanism, Three Consumers
The same command serves three audiences that usually need separate tooling.
Humans get a pre-commit hook and a fast local sanity check.
CI gets a cheap fail-early stage before the expensive stages.
Agents get structured feedback they can act on autonomously.
None of those required a separate implementation, because all three want the same underlying answer: does this parse, and if not, exactly where does it break?
Next in This Series
Part 3: Encrypted Config Secrets covers bxsecret:, the generatesecret CLI action, and how to manage the secret seed across a cluster without ending up with config you can no longer decrypt.
Add Your Comment