From cfb9c70259d3ea6200632e358cf6577082d683f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 01:23:19 +0000 Subject: [PATCH 1/2] Avoid absolute entry-point path in the BoxLang CLI launcher 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. --- run | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/run b/run index 10103a1..8f6ba60 100755 --- a/run +++ b/run @@ -19,8 +19,14 @@ # system/runners/BoxLangRunner.bx # passing through all CLI arguments unchanged. -# Get the path to the directory where this script is located -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +# Get the path to the directory where this script is located. +# Deliberately kept relative (no `cd ... && pwd`): BoxLang 1.17.0+ forces +# absolute-looking paths passed to the engine to resolve against +# mappings/webroot instead of the OS filesystem, which can make an +# absolute entry-point path fail to self-resolve during the run. A +# relative path avoids that, and also keeps server.cli.executionPath +# pointing at the caller's working directory instead of this directory. +DIR="$( dirname "${BASH_SOURCE[0]}" )" # Go baby go! -boxlang $DIR/system/runners/BoxLangRunner.bx $@ \ No newline at end of file +boxlang "$DIR/system/runners/BoxLangRunner.bx" "$@" \ No newline at end of file From 69dfbf3a829e415ad41fcee4ab0a06acb50336e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 01:32:54 +0000 Subject: [PATCH 2/2] Fix actual root cause: strip runner's own path from CLI positionals 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. --- run | 12 +++--------- system/runners/BoxLangRunner.bx | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/run b/run index 8f6ba60..10103a1 100755 --- a/run +++ b/run @@ -19,14 +19,8 @@ # system/runners/BoxLangRunner.bx # passing through all CLI arguments unchanged. -# Get the path to the directory where this script is located. -# Deliberately kept relative (no `cd ... && pwd`): BoxLang 1.17.0+ forces -# absolute-looking paths passed to the engine to resolve against -# mappings/webroot instead of the OS filesystem, which can make an -# absolute entry-point path fail to self-resolve during the run. A -# relative path avoids that, and also keeps server.cli.executionPath -# pointing at the caller's working directory instead of this directory. -DIR="$( dirname "${BASH_SOURCE[0]}" )" +# Get the path to the directory where this script is located +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # Go baby go! -boxlang "$DIR/system/runners/BoxLangRunner.bx" "$@" \ No newline at end of file +boxlang $DIR/system/runners/BoxLangRunner.bx $@ \ No newline at end of file diff --git a/system/runners/BoxLangRunner.bx b/system/runners/BoxLangRunner.bx index ea3d96d..90ed5f2 100644 --- a/system/runners/BoxLangRunner.bx +++ b/system/runners/BoxLangRunner.bx @@ -81,7 +81,21 @@ class{ // CLI Incoming variables var rootPath = server.cli.executionPath var options = server.cli.parsed.options - var positional = server.cli.parsed.positionals + // `server.cli.parsed.positionals` can include this runner's own script path as its + // first entry (the engine's argv[0]-equivalent), regardless of whether it was invoked + // with a relative or absolute path. Strip it so only genuine user-supplied positional + // arguments (e.g. `run my.bundle`) remain - otherwise every invocation with no bundle + // argument would misread its own path as the bundle to test. + var currentScriptPath = getCurrentTemplatePath() + var invocationDir = Paths.get( rootPath ) + var positional = server.cli.parsed.positionals.filter( ( p ) => { + try { + return invocationDir.resolve( p ).toRealPath().toString() != currentScriptPath + } catch( any e ) { + // If the candidate path doesn't exist on disk, it can't be this script - keep it. + return true + } + } ) var defaultReportPath = rootPath & "/tests/results" var dryRunValue = options[ "dry-run" ] ?: false var isDryRun = dryRunValue != false && dryRunValue != "false"