Skip to content

fix(cli): normalise Windows paths so CLI subcommands stop tripping Lucee Resource API#2843

Closed
wheels-bot[bot] wants to merge 4 commits into
developfrom
fix/bot-2841-wheels-cli-commands-on-windows-result-in-lucee-err
Closed

fix(cli): normalise Windows paths so CLI subcommands stop tripping Lucee Resource API#2843
wheels-bot[bot] wants to merge 4 commits into
developfrom
fix/bot-2841-wheels-cli-commands-on-windows-result-in-lucee-err

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

On Windows 11, wheels new, wheels start, and most other Wheels CLI
subcommands crashed before any work could happen with:

lucee.runtime.exp.NativeException: there is no Resource provider available
with the name [c], available resource providers are [ftp, zip, tar, tgz,
http, https, ram, s3]

Module.init() calls resolveProjectRoot(arguments.cwd), which feeds
java.io.File.getCanonicalPath() output (C:\Users\tim\Projects) plus a
/vendor/wheels suffix to directoryExists(). The mixed-slash string is
parsed by Lucee's Resource API as a URI with scheme c, which is not a
registered provider, so Lucee bails before any subcommand runs.

The fix adds a bootstrap-safe $normalizePath() helper to Module.cfc
(mirrored as the public Helpers.normalizePath() so it can be unit-tested)
that collapses backslashes to forward slashes, preserves a leading //
UNC / network-share prefix, and squashes doubled slashes from naive
concatenation. It is called on variables.cwd, variables.moduleRoot,
every interim candidate inside resolveProjectRoot(), the fallback return
value, and the targetDir that scaffoldNewApp() constructs. The
normalisation is a no-op on POSIX so Linux / macOS behaviour is unchanged.

wheels --version continued to work pre-fix because LuCLI handles the
--version flag upstream of module dispatch, never instantiating Module.

Fixes #2841.

Pull Request Checklist

  • Tests - cli/lucli/tests/specs/services/HelpersSpec.cfc gains five
    new normalizePath() cases under a regression-tagged describe() block.
    TDD path: spec added first asserting backslash to forward-slash conversion,
    POSIX no-op, empty input, doubled-slash collapse, and round-trip safety
    through & "/vendor/wheels" concatenation. Helper landed in
    cli/lucli/services/Helpers.cfc to make the assertions pass.
  • Framework Docs - left unchecked; bot-update-docs.yml follows up.
  • AI Reference Docs - left unchecked; bot-update-docs.yml follows up.
  • CLAUDE.md - left unchecked; bot-update-docs.yml follows up.
  • CHANGELOG.md - [Unreleased] -> Fixed entry added.
  • Test runner passes - caveat: bash tools/test-cli-local.sh was not
    executable in the bot sandbox (permissions blocked), so the
    failing-then-passing transition was verified by spec construction and a
    manual trace through $normalizePath() rather than a live run. CI will
    exercise both the framework and CLI suites on the matrix.

…cee Resource API

`wheels new`, `wheels start`, and most other subcommands crashed on
Windows 11 with `lucee.runtime.exp.NativeException: there is no
Resource provider available with the name [c]`. `Module.init()` fed
the output of `java.io.File.getCanonicalPath()` (e.g.
`C:\Users\tim\Projects`) plus a `/vendor/wheels` suffix to
`directoryExists()`; the mixed-slash result was parsed as a `c:`-scheme
URI, and Lucee bailed.

Add a bootstrap-safe `$normalizePath()` in `Module.cfc` (mirrored as the
public `Helpers.normalizePath()` so it can be unit-tested) that
collapses backslashes to forward slashes, preserves a leading `//` UNC
prefix, and squashes doubled slashes. Call it on `variables.cwd`,
`variables.moduleRoot`, every interim `candidate` in
`resolveProjectRoot()`, the fallback return value, and the `targetDir`
that `scaffoldNewApp()` constructs. No-op on POSIX.

Fixes #2841.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
…troubleshooting

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx — added a new troubleshooting entry under Windows: there is no Resource provider available with the name [c] explaining the mixed-slash path root cause and directing Windows users to update to the release containing Wheels CLI commands on Windows result in Lucee error #2841.

No changes were needed to .ai/wheels/ or CLAUDE.md — this is a CLI-internal path normalization fix with no impact on framework conventions or cross-engine patterns.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: This fix correctly addresses the Windows Lucee Resource API crash (#2841) by normalising backslashes in CLI paths before they reach directoryExists(). The approach is sound and the logic is correct for all realistic inputs. Three nits worth addressing before merge: a theoretical edge-case in the path-normaliser algorithm, a missing UNC test, and a CHANGELOG section ordering issue. Verdict: comment (no blocking findings).


Correctness

mid(rv, 3, len(rv) - 2) when rv == "//"cli/lucli/services/Helpers.cfc:134 and cli/lucli/Module.cfc (same logic in $normalizePath)

var body = len(leading) ? mid(rv, 3, len(rv) - 2) : rv;

When rv is exactly "//" (two characters only), len(rv) - 2 is 0, producing mid("//", 3, 0). CLAUDE.md invariant #8 flags Left(str, 0) as a Lucee 7 crash; mid() with a zero-length count falls into the same family of 0-length string extraction and may behave differently across engines. No realistic call path — java.io.File.getCanonicalPath() never returns "//" — but the function is public and its docblock says "bootstrap-safe." A free guard:

var body = (len(leading) && len(rv) > 2) ? mid(rv, 3, len(rv) - 2) : (len(leading) ? "" : rv);

This also applies to the identical block in Module.cfc::$normalizePath().


Tests

Missing UNC round-trip testcli/lucli/tests/specs/services/HelpersSpec.cfc

The code comments and docblock explicitly call out // UNC / network-share preservation as a design goal, and the leading variable exists solely for that case. However, there is no test exercising it:

it("preserves a UNC network-share prefix", () => {
    expect(helpers.normalizePath("//server/share/path"))
        .toBe("//server/share/path");
});

it("collapses doubled slashes inside a UNC path without eating the prefix", () => {
    expect(helpers.normalizePath("//server//share"))
        .toBe("//server/share");
});

Without a test, the UNC branch is covered only by code inspection.

Test runner not executed — the checklist notes:

bash tools/test-cli-local.sh was not executable in the bot sandbox (permissions blocked), so the failing-then-passing transition was verified by spec construction and a manual trace.

CI will catch regressions, but the CLAUDE.md table requires bash tools/test-cli-local.sh before reporting complete. Worth flagging so a human reviewer can confirm it goes green on the matrix.


Docs

CHANGELOG section orderCHANGELOG.md:22-27

Keep a Changelog convention (which this file follows, per the header) puts sections in this order: Added → Changed → Deprecated → Removed → Fixed → Security. The existing released entries (e.g. 4.0.2) follow that order. This PR places ### Fixed above ### Added in [Unreleased]:

### Fixed          ← added by this PR (above Added)

### Added          ← pre-existing RustCFML entry

### Changed        ← pre-existing

Moving the ### Fixed block below ### Added and ### Changed keeps the file consistent with the rest of the changelog and the keep-a-changelog spec.


Design note (non-blocking)

$normalizePath in Module.cfc and normalizePath in Helpers.cfc are byte-for-byte identical, held together by a "the two MUST stay in lock-step" comment in the docblock. The PR explains the chicken-and-egg reason: Helpers is lazy-initialised after variables.projectRoot is set, so it cannot be called during bootstrap. That's valid. A TODO comment or a shared constant for the regex pattern ("/{2,}") would reduce the risk of silent drift — but this is advisory, not a blocker.


@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: The only new commit since the previous review (9681c0c) is `eb2baa3ac` — a documentation addition that inserts a Windows troubleshooting section into the CLI install guide. The docs content is accurate and well-placed. The code changes from the first commit are unchanged, so the three nits flagged in the previous review are still open. No correctness, cross-engine, or security blockers. Verdict: comment.


Docs (new commit only)

web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx:234-246

The new troubleshooting block is accurate: the error message is reproduced verbatim, the root-cause explanation (mixed-slash string → Lucee Resource API scheme mis-parse) matches the fix, and the remediation step ("update to the latest version") is correct. Scoop is called out by name with the right invocation. No issues.


Correctness (carry-over from previous review, still unresolved)

cli/lucli/Module.cfc and cli/lucli/services/Helpers.cfc$normalizePath() / normalizePath()mid() with zero count

When path is exactly "//" (two chars), len(rv) - 2 is 0 and the code calls mid(rv, 3, 0). CLAUDE.md invariant #8 flags Left(str, 0) as a Lucee 7 crash; Mid(str, n, 0) is a different function and may behave correctly, but the path through which "//" could reach this function in production does not exist — java.io.File.getCanonicalPath() never returns "//". Low risk, but a one-line guard would be clean:

var body = (len(leading) && len(rv) > 2) ? mid(rv, 3, len(rv) - 2) : (len(leading) ? "" : rv);

Apply to both the Module.cfc::$normalizePath() and Helpers.cfc::normalizePath() copies.


Tests (carry-over, still unresolved)

cli/lucli/tests/specs/services/HelpersSpec.cfc — missing UNC round-trip

The UNC / network-share branch (leading = "//") exists solely to preserve //server/share style paths on Windows, and the docblock calls it out explicitly. None of the five new specs exercise it:

it("preserves a UNC network-share prefix", () => {
    expect(helpers.normalizePath("//server/share/path"))
        .toBe("//server/share/path");
});

it("collapses doubled slashes inside a UNC path without eating the prefix", () => {
    expect(helpers.normalizePath("//server//share"))
        .toBe("//server/share");
});

Without these, the leading branch is covered only by code inspection.


Conventions (carry-over, still unresolved)

CHANGELOG.md:22-27 — section order

Keep a Changelog mandates: Added → Changed → Deprecated → Removed → Fixed → Security. The [Unreleased] block currently has Fixed above Added:

### Fixed     ← this PR (wrong position)

### Added     ← pre-existing

### Changed   ← pre-existing

Every released entry in this file (e.g. 4.0.2) follows the canonical order. Move ### Fixed below ### Changed to stay consistent.


Commits

Both commits conform to commitlint.config.js:

SHA Message Verdict
9681c0c fix(cli): normalise Windows paths so CLI subcommands stop tripping Lucee Resource API OK
eb2baa3 docs(web/guides): add Windows Resource-provider crash to CLI install troubleshooting OK

Subjects are under 100 chars, sentence-case, valid types, no ALL-CAPS.

@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review catches two real nits (CHANGELOG order, missing UNC test) and issues a "comment" verdict that fits the evidence. However, one finding is a probable false positive, and there's a coverage gap from a SHA timing mismatch that A should acknowledge.

Sycophancy

None detected. A did not approve without evidence; the "comment" verdict is proportionate to the findings.

False positives

mid() 0-length claim extends invariant #8 beyond its documented scope.

A wrote:

mid() with a zero-length count falls into the same family of 0-length string extraction and may behave differently across engines.

CLAUDE.md invariant #8 documents exactly one crash: "Left(str, 0) crashes Lucee 7." It does not mention Mid(). Mid(string, start, 0) returns an empty string safely on Lucee, Adobe CF, and BoxLang — that's standard CFML behavior and is not in the documented crash list. Extrapolating the Left() bug to Mid() without evidence is speculation.

The actual code path where rv == "//" is admitted to be unreachable in practice (A's own words: "No realistic call path"). A's proposed guard is harmless, but flagging it as a cross-engine hazard misrepresents the risk level and could cause unnecessary churn in address-review. This should be retracted or downgraded to a pure style suggestion, not a correctness concern citing CLAUDE.md.

Missed issues

A's review marker references a stale SHA (9681c0c1f) while the submitted commit_id is eb2baa3ac (current head). The docs commit (docs(web/guides): add Windows Resource-provider crash...) was pushed by bot-update-docs.yml between A's trigger and A's post, so A reviewed only the fix commit and never evaluated the MDX addition in web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx. The content there is factually accurate and introduces no bugs, but the gap means A's "reviewed" claim only covers one of two commits on this PR. A should acknowledge this explicitly rather than implicitly claiming full coverage.

No other missed issues on the fix commit itself. The commit messages pass commitlint (type fix/docs, scopes cli/web/guides, headers well within 100 chars).

Verdict alignment

A's "comment" verdict is correct given the nature of the findings (changelog ordering, missing test). Nothing here blocks merge once the nits are addressed.

Convergence

Not aligned. The mid() false positive needs A to either produce evidence that Mid(str, n, 0) is documented as unsafe on Lucee 7 (in which case the finding stands) or retract/downgrade it. Once that's resolved, the remaining two actionable items (CHANGELOG order, UNC test) are solid and point toward a narrow converged-changes outcome. A should also note the SHA coverage gap in its response.

@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review is accurate and well-structured. All three carry-over nits are genuine findings backed by the actual diff. No sycophancy, no false positives, and no missed issues of consequence. A's comment verdict is defensible for the severity level, and since the spec explicitly covers comment with concrete actionable findings as qualifying for convergence-with-changes, this resolves cleanly.

Sycophancy

None detected. A did not approve and cited evidence for every finding.

False positives

None detected.

  • mid(rv, 3, 0) claim — verified against the actual code. When path == "//", len(rv) - 2 == 0, so mid(rv, 3, 0) is genuinely reached. A correctly distinguishes this from CLAUDE.md invariant adding better description for hasManyCheckBox #8 (Left(str, 0)) and correctly marks it low-risk (canonical paths never equal "//"). Finding stands.
  • Missing UNC tests — verified: the five new specs in HelpersSpec.cfc cover backslash conversion, POSIX no-op, empty input, doubled-slash collapse, and drive-letter round-trip. None pass a //server/share/... input. The leading branch is tested only by code inspection. Finding stands.
  • CHANGELOG section order — verified against the diff. The new ### Fixed block lands above the pre-existing ### Added and ### Changed blocks in [Unreleased]. Every released entry in the file follows Added → Changed → Fixed. Finding stands.

Missed issues

None detected. Cross-engine risk is low (CLI runs under Lucee only; replace(), left(), mid(), reReplace() are all standard CFML). The core fix logic is correct. Commits pass commitlint. Docs content is accurate.

Verdict alignment

A's comment verdict is consistent with findings that are real but non-blocking — no correctness or security blockers, three small mechanical fixes.

Convergence

Aligned — changes needed. A's three findings are all correct and each has a concrete fix:

  1. Guard mid(rv, 3, len(rv) - 2) in both Module.cfc::$normalizePath() and Helpers.cfc::normalizePath() against the len(rv) == 2 case (e.g. var body = (len(leading) && len(rv) > 2) ? mid(rv, 3, len(rv) - 2) : (len(leading) ? "" : rv);).
  2. Add two UNC specs to cli/lucli/tests/specs/services/HelpersSpec.cfc: normalizePath("//server/share/path") == "//server/share/path" and normalizePath("//server//share") == "//server/share".
  3. Move the ### Fixed block below ### Changed in the [Unreleased] section of CHANGELOG.md.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A response (round 1)

B raised two issues: a false-positive call on my mid() finding and a SHA coverage gap. I concede the mid() false-positive in full. I defend the SHA-gap claim: my second A review was posted 2 minutes before B's critique and explicitly evaluated the docs commit. The two remaining actionable nits (CHANGELOG order, missing UNC tests) were not challenged and stand. Verdict stays at comment.

Conceded points

  • mid() cross-engine concern is a false positive. B is correct. CLAUDE.md invariant #8 documents exactly one crash: `Left(str, 0)` crashes Lucee 7. It does not mention Mid(). Extrapolating that documented Left() crash to Mid(str, n, 0) — which returns an empty string by spec on all three engine families — was speculation without evidence. I retract the cross-engine correctness framing entirely. The one-line guard I proposed is unobjectionable as a pure style preference (the unreachable "//" input would produce an empty body, which is harmless), but it carries zero correctness weight and should not be flagged as a cross-engine hazard in address-review. I withdraw that finding.

Defended points

  • SHA coverage gap is already closed. B writes that "A never evaluated the MDX addition" because the first review referenced stale SHA 9681c0c1f. That was true at the moment the first review was posted — but I submitted a second A review at 2026-06-02T19:48:34Z, two minutes before B's critique at 2026-06-02T19:50:10Z, carrying the marker wheels-bot:review-a:2843:eb2baa3ac1c9bb0e88ac9d52bf11709b88652aa2. That review explicitly evaluated web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx:234-246 and found the content accurate and well-placed. The gap existed transiently; it was closed before B's critique was posted. No further coverage action needed.

  • CHANGELOG section order nit stands. Keep a Changelog mandates Added → Changed → Deprecated → Removed → Fixed → Security. The [Unreleased] block currently has ### Fixed above ### Added and ### Changed. Every released entry in the file (e.g. 4.0.2) follows the canonical order. B did not challenge this. It remains a valid, low-effort fix.

  • Missing UNC round-trip tests stand. The leading = "//" branch in both $normalizePath() and normalizePath() exists solely to preserve UNC paths on Windows, and the docblock calls this out explicitly. None of the five new specs in HelpersSpec.cfc exercise that branch. B did not challenge this finding. A //server/share/path round-trip and a doubled-slash-inside-UNC test are the right additions.

Updated verdict

Still comment: two nits (CHANGELOG ordering and UNC test coverage) remain open and are easy to address; once those are resolved the PR is approvable. The retracted mid() concern removes any ambiguity about correctness blockers — there are none.

- Reorder CHANGELOG `[Unreleased]` block to keep-a-changelog order
  (Added -> Changed -> Fixed), placing the #2841 Fixed entry below
  the pre-existing RustCFML Added and version-switcher Changed
  entries (CHANGELOG.md).
- Add two UNC-prefix specs to `normalizePath()` in
  cli/lucli/tests/specs/services/HelpersSpec.cfc covering the
  `leading == "//"` branch: round-trip preservation of
  `//server/share/path` and doubled-slash collapse inside a UNC
  path (`//server//share` -> `//server/share`).

Skipped: the `mid(rv, 3, 0)` zero-count guard that Reviewer A
flagged in round 0 was explicitly retracted by A in its round-1
response (extrapolation of CLAUDE.md invariant #8 beyond its
documented Left() scope, with no reachable call path).

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Address Review (round 1)

Applied consensus findings from Reviewer A and Reviewer B's convergence (round 1):

  • CHANGELOG section order — moved the ### Fixed block in [Unreleased] to sit below the pre-existing ### Added (RustCFML, feat(engine): recognize RustCFML + degrade caching when cfcache is absent #2837) and ### Changed (version switcher, docs(web): consolidate 4.0 docs to one v4.0 line, drop stale snapshot #2827) blocks, matching keep-a-changelog ordering and every released entry in the file (CHANGELOG.md:21-31).
  • Missing UNC round-trip specs — added two new it() blocks inside the describe("normalizePath()", ...) group in cli/lucli/tests/specs/services/HelpersSpec.cfc:140-148: normalizePath("//server/share/path") == "//server/share/path" (preserves the leading UNC prefix) and normalizePath("//server//share") == "//server/share" (collapses doubled slashes inside a UNC path without eating the prefix). These exercise the leading == "//" branch in Helpers.cfc::normalizePath() and Module.cfc::$normalizePath() that was previously only covered by code inspection.

Skipped (not in the consensus):

  • mid(rv, 3, 0) zero-count guard on the rv == "//" edge case — skipped: Reviewer A retracted this finding in its round-1 response, conceding it was an unsupported extrapolation of CLAUDE.md invariant adding better description for hasManyCheckBox #8 (Left(str, 0) crashes Lucee 7) onto Mid(), which returns an empty string safely on Lucee/Adobe/BoxLang. A's own analysis noted there is no realistic call path that produces rv == "//" because java.io.File.getCanonicalPath() never returns it. With A withdrawing the cross-engine framing, the proposed guard carries zero correctness weight and was not part of the converged-changes set.

Test runner caveat: bash tools/test-cli-local.sh was not approvable in the bot sandbox for this round (same constraint as the original PR's checklist). Spec construction was verified by manual trace through Helpers.cfc::normalizePath():

  • //server/share/pathrv == "//server/share/path"leading == "//"body == mid(rv, 3, 17) == "server/share/path" → no doubled slashes to collapse → returns "//server/share/path"
  • //server//sharerv == "//server//share"leading == "//"body == mid(rv, 3, 13) == "server//share"reReplace(..., "/{2,}", "/", "all") == "server/share" → returns "//server/share"

CI's CLI matrix will exercise both new specs on a live runtime.

The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on approve or the outer-loop cap (5 rounds) is reached.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: The round-1 fix-up commit (08bc9c440) closes both outstanding nits from the previous review cycle — CHANGELOG section ordering is now Added → Changed → Fixed, and two UNC-prefix specs have been added covering the leading == "//" branch. All three correctness, cross-engine, and security checks remain clear. The PR is approvable; GitHub blocks self-review so this is posted as a comment. Verdict: approve.


What changed in the latest commit

08bc9c440 addresses exactly what the A/B consensus asked for:

  • CHANGELOG reordering: ### Fixed now sits below ### Added and ### Changed in [Unreleased], matching keep-a-changelog canonical order and every released entry in the file (e.g. 4.0.2). Verified at CHANGELOG.md:23-31.
  • UNC specs added: cli/lucli/tests/specs/services/HelpersSpec.cfc gains "preserves a UNC network-share prefix" and "collapses doubled slashes inside a UNC path without eating the prefix". Both traces through the algorithm are correct:
    • "//server/share/path"leading="//", body="server/share/path", return "//server/share/path"
    • "//server//share"leading="//", body="server//share" → after reReplace"server/share", return "//server/share"

Correctness

No new issues. The two previously disputed points are resolved:

  • The mid(rv, 3, 0) zero-count concern was correctly retracted (CLAUDE.md invariant #8 documents only Left(str, 0) as a Lucee 7 crash; Mid(str, n, 0) returns "" by spec on all engines, and the rv == "//" input is unreachable from java.io.File.getCanonicalPath() in any case).
  • The core fix — normalising variables.cwd, variables.moduleRoot, every candidate in resolveProjectRoot(), and targetDir in scaffoldNewApp() — is correct and complete.

Nits (non-blocking, informational)

Signature drift between the two copiescli/lucli/Module.cfc:$normalizePath declares string path = "" (optional, defaults to empty), while cli/lucli/services/Helpers.cfc:normalizePath declares required string path. The docblock says the two "MUST stay in lock-step," but their argument contracts diverge. The behavioral output for any actual input is identical (both short-circuit on !len(arguments.path)), so this is not a bug. Worth noting: a no-arg call to $normalizePath() silently returns "", while a no-arg call to helpers.normalizePath() throws. If "lock-step" is meant to include the contract, align to required string path on both.

Backslash UNC form not tested\\server\share (Windows backslash UNC) is another valid input. Tracing the algorithm: replace converts both backslashes to //, then the leading branch preserves the prefix and body-collapse handles the rest — output is //server/share. This case follows the same code path as the forward-slash UNC tests, so coverage is adequate by inference. A spec for it would add clarity but is not required.


Commits

All three commits conform to commitlint.config.js and carry DCO sign-off:

SHA Message DCO
9681c0c fix(cli): normalise Windows paths so CLI subcommands stop tripping Lucee Resource API
eb2baa3 docs(web/guides): add Windows Resource-provider crash to CLI install troubleshooting
08bc9c4 fix(cli): address Reviewer A/B consensus findings (round 1)

Subjects are sentence-case, under 100 chars, valid types, no ALL-CAPS.

@wheels-bot

wheels-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review of the round-1 fix-up commit is accurate and well-supported. Every claim is verifiable against the diff or the git log, and the approve verdict is proportionate to what the commit actually changed.

Sycophancy

None detected. A cited specific algorithm traces, a line range for CHANGELOG verification, and a commit-by-commit DCO table before issuing the approve verdict.

False positives

None detected.

  • CHANGELOG section order — verified independently by reading CHANGELOG.md at the PR head. The [Unreleased] block is ### Added (RustCFML, feat(engine): recognize RustCFML + degrade caching when cfcache is absent #2837) → ### Changed (version switcher, docs(web): consolidate 4.0 docs to one v4.0 line, drop stale snapshot #2827) → ### Fixed (Windows path, Wheels CLI commands on Windows result in Lucee error #2841). Correct keep-a-changelog order. A's reference to CHANGELOG.md:23-31 is accurate.
  • UNC spec traces — both traces check out. //server/share/path: leading="//", body=mid(rv,3,17)="server/share/path", no doubles to collapse, returns "//server/share/path" ✓. //server//share: leading="//", body=mid(rv,3,13)="server//share", reReplace collapses to "server/share", returns "//server/share" ✓.
  • Signature drift nit — verified against the diff. Module.cfc::$normalizePath declares string path = "" (optional, defaults to empty); Helpers.cfc::normalizePath declares required string path. A correctly notes that the behavioral output is identical for any non-empty input (both short-circuit on !len(arguments.path)), making this a contract divergence rather than a bug.
  • Retraction of mid() concern — appropriate. The mid(rv, 3, 0) edge case (rv == "//") was raised in an earlier round, then retracted as an unsupported extrapolation of CLAUDE.md invariant adding better description for hasManyCheckBox #8 (Left(str,0) on Lucee 7). Mid(str, n, 0) returns "" safely on all engines and the input is unreachable from java.io.File.getCanonicalPath(). Not re-raising it is correct.
  • DCO sign-off — verified via git log. All three commits carry Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>.

Missed issues

None detected.

  • A did not review the installation.mdx docs addition in detail, but the content is factually accurate and introduces no bugs or broken links. The omission is not consequential.
  • Backslash UNC (\\server\share) coverage by inference is sound: backslash replacement runs first, converting \\server\share//server/share, after which the identical code path as the new forward-slash UNC specs applies. An explicit spec would add clarity but is not required.
  • No commitlint violations: all three commits use valid types (fix, docs, fix), valid scopes (cli, web/guides, cli), subjects are sentence-case and under 100 characters.

Verdict alignment

A's approve verdict is consistent with the findings: no correctness blockers, no security concerns, two prior nits addressed, two informational nits remaining that are explicitly non-blocking.

Convergence

Aligned — no changes needed. A's findings are accurate, the approve verdict is correct, and no actionable gaps remain. The PR is review-clean at this SHA.

@bpamiri bpamiri marked this pull request as ready for review June 3, 2026 16:07

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: The only change since the last reviewed SHA (08bc9c440) is a single merge commit (eeea8169) that syncs develop into this branch. The merge was clean: no code files were touched, both parents' changes to installation.mdx are correctly integrated, and the [Unreleased] CHANGELOG section ordering (Added → Changed → Fixed) introduced in 08bc9c440 is preserved. All findings from the previous review cycle are still resolved. No new correctness, cross-engine, or security concerns. Verdict: approve (GitHub blocks self-review; posting as comment).


What the merge commit brings in

Commit eeea8169 merges develop at 7ccff9588 (fix(distribution): dearmor apt key in docs + stop stable apt index clobber on BE publish (#2846)). The 11 files it touches are all documentation and distribution-draft files — no CLI source (Module.cfc, Helpers.cfc, HelpersSpec.cfc) was modified by the merge.

The one file changed by both parents is web/sites/guides/src/content/docs/v4-0-0/command-line-tools/installation.mdx:

  • Develop side: swapped tee to gpg --dearmor in the apt key-installation snippets (lines 55, 72) and expanded the GPG-signing aside (line 88).
  • Feature-branch side: added the Windows there is no Resource provider available with the name [c] troubleshooting section (lines 234-246).

Both sets of changes are present in the merged result. No content was lost or corrupted in resolution.


CHANGELOG (carry-forward confirmation)

CHANGELOG.md at eeea8169 reads:

### Added
[RustCFML entry]

### Changed
[version-switcher entry]

### Fixed
[Windows CLI crash entry — this PR]
[apt dearmor fix — #2838, from develop]
[apt stable-index clobber fix — #2838, from develop]

Order is Added → Changed → Fixed, matching keep-a-changelog canonical order and every released entry in the file. The apt entries landed under ### Fixed, which is the correct section; no reordering needed.


Previously resolved nits (still closed)

  • CHANGELOG section order — fixed in 08bc9c440; preserved through the merge.
  • UNC round-trip specscli/lucli/tests/specs/services/HelpersSpec.cfc retains the two UNC cases added in 08bc9c440; untouched by the merge.
  • mid() concern — retracted in the A/B round; no change needed.

Commits

SHA Message Note
9681c0c fix(cli): normalise Windows paths so CLI subcommands stop tripping Lucee Resource API OK
eb2baa3 docs(web/guides): add Windows Resource-provider crash to CLI install troubleshooting OK
08bc9c4 fix(cli): address Reviewer A/B consensus findings (round 1) OK
eeea816 Merge branch 'develop' into fix/bot-2841-... Standard merge message; commitlint does not apply to merge commits

@wheels-bot

wheels-bot Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review of the merge commit is accurate and well-supported. All specific claims check out against the diff and git history. Approve verdict is correct.

Sycophancy

None detected. A walked through the merge content explicitly — specific files touched, line ranges in CHANGELOG, merge-resolution confirmation — before issuing the verdict.

False positives

None detected.

  • "11 files, all documentation and distribution-draft files" — verified via git show --stat eeea8169. Exactly 11 files: CHANGELOG.md, six files under tools/distribution-drafts/apt-repo/ and tools/distribution-drafts/linux-packages/, a blog post, and three MDX guides. No CLI source touched. A's characterization is precise.
  • "no CLI source (Module.cfc, Helpers.cfc, HelpersSpec.cfc) was modified by the merge" — confirmed. Those files appear in the cumulative PR diff from earlier commits (9681c0c, 08bc9c4); they are not in the merge commit's stat.
  • CHANGELOG order (Added → Changed → Fixed) — confirmed against the diff. [Unreleased] reads Added (RustCFML) → Changed (version-switcher) → Fixed (Windows CLI crash + two apt entries from develop). Correct per keep-a-changelog.
  • installation.mdx merge resolution — the merge stat shows 6 +++--- on that file. The PR diff confirms both the feature-branch Windows troubleshooting section and the develop-side apt key changes are present with no content lost.
  • "GitHub blocks self-review" — correct. The bot is the PR author; GitHub prevents the PR author from submitting a formal approve review on their own PR regardless of who authored the head commit.

Missed issues

None detected.

  • DCO does not apply to merge commits; the DCO App is standard-configured to skip them. The three bot-authored commits (9681c0c, eb2baa3, 08bc9c4) were already confirmed to carry sign-offs in the prior B round.
  • commitlint correctly does not apply to merge commits.
  • No cross-engine concerns: the merge brings in only MDX, shell scripts, and YAML — no CFML.

Verdict alignment

A's approve verdict is consistent with the findings: a clean merge of documentation and distribution tooling with no regressions in any previously addressed nit.

Convergence

Aligned — no changes needed. The merge commit is clean, prior fixes hold, and A's approve is correct. The PR is review-clean at this SHA.

@bpamiri

bpamiri commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #2835 — value salvaged in #2863

Closing without merging. While this PR was in flight, #2835 landed on develop and fixes the same root cause (#2841): the Windows mixed-slash path that makes Lucee's Resource API read c: as a URI scheme.

#2835 took a defense-in-depth approach — $normalizePath() plus a $safeDirExists() java.io.File fallback — so the user-facing crash is already resolved on develop. That left this PR's remaining value as test coverage + docs rather than the fix itself, and its heavy overlap with #2835 (a naïve conflict resolution would have reverted the $safeDirExists layer) made a clean merge impractical.

The durable parts are carried into #2863:

  • the canonical normalizePath() is consolidated into Helpers.cfc — one tested implementation, with Module.$normalizePath() delegating to it, so the regression specs exercise the real bootstrap path rather than a parallel copy;
  • the 7 normalizePath() regression specs;
  • the Windows "Resource provider [c]" troubleshooting section in the CLI install guide.

Thanks for the thorough work here — it directly informed the follow-up.

@bpamiri bpamiri closed this Jun 5, 2026
bpamiri added a commit that referenced this pull request Jun 5, 2026
…docs (#2863)

* refactor(cli): unify path normalization, add tests + Windows install docs

Follow-up to #2835, which fixed the Windows "no Resource provider [c]"
crash (#2841). This does not change the runtime fix #2835 shipped:

- Move the canonical normalizePath() into Helpers.cfc (its natural home
  beside capitalize/pluralize/stripSpecialChars) and reduce
  Module.$normalizePath() to a one-line delegating wrapper. Helpers is a
  dependency-free leaf utility, so it is safe to construct during init().
  The 6 call sites and the $safeDirExists() fallback are unchanged.
- Add 7 normalizePath() regression specs to HelpersSpec.cfc. Because
  Module delegates to Helpers now, these exercise the real bootstrap path
  rather than a parallel copy.
- Document the Windows "there is no Resource provider available with the
  name [c]" failure in the CLI installation guide.

Supersedes #2843. Verified locally: 667/667 cli/lucli specs pass.

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(cli): guard normalizePath mid() edge and broaden Windows update docs

Address Reviewer A on #2863:

- Guard the mid(rv, 3, len(rv) - 2) call in Helpers.normalizePath()
  against a count of 0 when rv is exactly "//" — cf. CLAUDE.md
  cross-engine invariant #8 (Left(str, 0) crashes Lucee 7; mid() with a
  zero count and out-of-range start may do the same). This PR introduced
  the mid() into the bootstrap path via delegation, so the guard sits on
  the path that actually runs. Output is unchanged for every real input.
- Add a regression spec for the degenerate "//" root.
- Broaden the Windows troubleshooting note so manual JAR installs get an
  update path too, not just Scoop.

Verified locally: 668/668 cli/lucli specs pass.

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Signed-off-by: Peter Amiri <peter@alurium.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wheels CLI commands on Windows result in Lucee error

1 participant