Skip to content

TESTBOX-453: Fix BoxLang CLI runner misreading its own path as a bundle argument - #201

Merged
lmajano merged 2 commits into
developmentfrom
fix/boxlang-cli-runner-absolute-path
Aug 29, 2026
Merged

TESTBOX-453: Fix BoxLang CLI runner misreading its own path as a bundle argument#201
lmajano merged 2 commits into
developmentfrom
fix/boxlang-cli-runner-absolute-path

Conversation

@lmajano

@lmajano lmajano commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Description

./testbox/run --verbose --stream (the BoxLang CLI runner) fails immediately, before any test spec runs, with:

[ERROR] [EXCEPTION] The requested class [.../testbox/system/runners/BoxLangRunner.bx] has not been located in the [bx] resolver.
ortus.boxlang.runtime.types.exceptions.ClassNotFoundBoxLangException: ...
	at ortus.boxlang.runtime.loader.ClassLocator.load(ClassLocator.java:587)
	at ortus.boxlang.runtime.bifs.global.system.GetClassMetadata._invoke(GetClassMetadata.java:66)
	...
	at boxgenerated.boxclass.testbox.system.Testbox$cfc.invokeFunction_runRaw(.../TestBox.cfc:454)
	...
	at boxgenerated.boxclass...Boxlangrunner$bx.invokeFunction_main(.../BoxLangRunner.bx:219)
	at ortus.boxlang.runtime.BoxRuntime.executeClass(BoxRuntime.java:1466)
	at ortus.boxlang.runtime.BoxRunner._main(BoxRunner.java:255)

This surfaced downstream in ortus-boxlang/bx-sites CI on ubuntu-latest, reported as intermittent (~1/3 of runs) against BoxLang engine build v1.17.0+58.

Root cause: On the affected BoxLang build, server.cli.parsed.positionals always includes the entry-point script's own invocation path as its first entry (an argv[0]-style leak), regardless of whether boxlang was invoked with a relative or absolute path. BoxLangRunner.bx's CLI handling assumed any non-empty positionals meant the user passed a bundle name positionally:

if( positional.len() ) {
    initArgs.bundles = positional[ 1 ]
    initArgs.directory = {}
}

Since positionals is never actually empty on this engine build, an ordinary ./testbox/run --verbose --stream invocation (no bundle argument) has positional[1] equal to the runner's own script path, so TestBox is told to test its own runner file as a "bundle" — which then fails to resolve as a class in TestBox.cfc's bundle-discovery loop.

I reproduced this locally 100% of the time (installed BoxLang v1.17.0+58 via the quick installer and built a scratch project mirroring bx-sites), traced it with instrumentation, and confirmed the fix resolves it under both relative and absolute boxlang invocation, with 8/8 consecutive green runs afterward.

Fix: filter server.cli.parsed.positionals to drop any entry that resolves (relative to server.cli.executionPath, following symlinks) to the currently-executing script, before treating what remains as a genuine user-supplied bundle argument.

Jira Issues

https://ortussolutions.atlassian.net/browse/TESTBOX-453

Type of change

  • Bug Fix

Checklist

  • My code follows the style guidelines of this project cfformat
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Note: this fix touches the CLI runner's argument parsing, which isn't exercised by TestBox's existing test suite (that runs against CFML engine web servers, not the BoxLang CLI entry point) — verification here was a manual local repro against the actual BoxLang engine build, described above and in the linked Jira issue.


Generated by Claude Code

claude added 2 commits August 29, 2026 01:23
The `run` launcher resolved its own directory to an absolute path
before invoking `boxlang`, so BoxLangRunner.bx was executed via an
absolute filesystem path. BoxLang 1.17.0 hardened path resolution to
force absolute-looking paths through mappings/webroot instead of the
OS filesystem, which intermittently broke the entry point's own
self-resolution later in the run (surfacing as a ClassNotFoundBoxLangException
during TestBox's bundle discovery) on CI runners invoking `./testbox/run`.

Passing boxlang a relative path avoids exercising that code path, and
leaves server.cli.executionPath pointing at the caller's working
directory as before.
Installed BoxLang v1.17.0+58 (the exact engine build from the CI
failure reports) and reproduced the crash locally with a scratch
project mirroring bx-sites' setup. Instrumented TestBox.cfc and
BoxLangRunner.bx to trace the failure and found the real cause:
`server.cli.parsed.positionals` on this engine build always includes
the entry-point script's own invocation path as its first entry
(effectively an argv[0] leak), regardless of whether boxlang was
invoked with a relative or absolute path.

BoxLangRunner.bx's `if( positional.len() ) { initArgs.bundles =
positional[1] }` therefore always treats a plain `./testbox/run
--verbose --stream` invocation (no bundle argument) as if the user
had passed the runner's own path as a bundle to test, which then
fails GetClassMetadata() resolution downstream in TestBox.cfc's
bundle-discovery loop with the reported ClassNotFoundBoxLangException.

This supersedes the previous commit on this branch, which changed
`run` to pass a relative path under the theory that BoxLang 1.17.0's
include-path hardening was responsible. That was disproven by testing:
the crash reproduces identically with a relative invocation, and the
real fix below resolves it under both relative and absolute
invocation. `run` is reverted to its original form.

Fix: filter `server.cli.parsed.positionals` to drop any entry that
resolves (relative to the invocation directory, following symlinks)
to this same script, before treating what remains as a user-supplied
bundle argument. Verified locally: 8/8 consecutive green runs via
`./testbox/run --stream`, and explicit `run my.bundle`-style
invocations still work correctly.
@github-actions

Copy link
Copy Markdown

lucee@6 21 Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

lucee@7 21 Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

boxlang-cfml@1 21 Test Results

  1 files  ±0   29 suites  ±0   8s ⏱️ -1s
735 tests ±0  714 ✅ ±0  21 💤 ±0  0 ❌ ±0 
751 runs  ±0  720 ✅ ±0  31 💤 ±0  0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

boxlang@be 21 Test Results

  1 files  ±0   29 suites  ±0   9s ⏱️ -1s
734 tests ±0  713 ✅ ±0  21 💤 ±0  0 ❌ ±0 
751 runs  ±0  719 ✅ ±0  32 💤 ±0  0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

boxlang@1 21 Test Results

  1 files  ±0   29 suites  ±0   6s ⏱️ -1s
734 tests ±0  713 ✅ ±0  21 💤 ±0  0 ❌ ±0 
751 runs  ±0  719 ✅ ±0  32 💤 ±0  0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

adobe@2023 21 Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@github-actions

Copy link
Copy Markdown

adobe@2025 21 Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 69dfbf3. ± Comparison against base commit 88ea7ac.

@lmajano
lmajano merged commit 3499a24 into development Aug 29, 2026
24 checks passed
lmajano pushed a commit to ortus-boxlang/bx-sites that referenced this pull request Aug 29, 2026
tests.yml's test suite was intermittently failing before a single spec
ran, with ClassNotFoundBoxLangException on TestBox's own
BoxLangRunner.bx - blocking this PR's own CI (and any other PR's)
independently of the registerMapping fix above.

Root cause turned out to be a real TestBox bug, not a BoxLang engine
issue: server.cli.parsed.positionals leaks the runner's own invocation
path as argv[0] on current engine builds, and BoxLangRunner.bx's
naive `positional[1]` grab misread that as a user-supplied bundle
argument whenever none was actually given. Fixed upstream in
Ortus-Solutions/TestBox#201 (reproduced and verified locally there).

Pin id=testbox to id=testbox@be so CI picks up that fix from the
bleeding-edge channel now, rather than waiting on a tagged stable
TestBox release. Revert to the unpinned id=testbox once one ships it.
lmajano added a commit that referenced this pull request Aug 29, 2026
#202)

Reported by the maintainer running ./testbox/run --stream --verbose
against BoxLang v1.17.0+58: every CLI run crashed immediately after
the recent CLI-runner fix (#201) cleared the way to reach this code,
with:

  KeyNotFoundException: The requested key [url] was not located in
  any scope or it's undefined
    at ...ScriptingRequestBoxContext.scopeFind
    at ...Testbox$cfc.invokeFunction_runRaw(TestBox.cfc:408)

Root cause: runRaw() and dryRun() unconditionally reference the `url`
scope to support web-request query-string test filters (?testBundles=,
?testSuites=, etc.). The `url` scope only exists in a real HTTP
request context - it is never registered at all when TestBox runs via
the BoxLang CLI. #200 ("support full null runtimes") added a
`structKeyExists( url, "testBundles" )` guard, but that doesn't help
here: resolving the bare `url` identifier itself is what throws -
structKeyExists() never gets a chance to run, since BoxLang has to
look up `url` as a scope before it can pass it as an argument.

Fix: skip these URL-based filter blocks entirely when
variables.IS_CLI is true (already computed at class init). CLI users
already have --filter-bundles/--filter-suites/--filter-specs for the
same purpose via BoxLangRunner.bx, so nothing is lost.

Verified locally against BoxLang v1.17.0+58: reproduced the exact
reported crash against the real merged development branch first
(structKeyExists guard included), then confirmed the fix with the
maintainer's exact command (./testbox/run --streamingj --verbose,
4/4 clean runs), plus --dry-run and --stream, which exercise both
patched call sites (runRaw() and dryRun()).

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants