Skip to content

fix(packages): use java.io.File.mkdirs() for cache dirs so Adobe CF works - #2598

Merged
bpamiri merged 3 commits into
developfrom
claude/angry-franklin-55d865
May 12, 2026
Merged

bpamiri merged 3 commits into
developfrom
claude/angry-franklin-55d865

Conversation

@bpamiri

@bpamiri bpamiri commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ManifestCache.$ensureDir was calling DirectoryCreate(path, true). The createPath flag is a Lucee extension — Adobe CF rejects the second argument with "The function takes 1 parameter", crashing the Tools → Packages page on every fresh ACF install (Packages page fails on Adobe ColdFusion: DIRECTORYCREATE called with unsupported arguments #2567).
  • Routes the recursive mkdir through java.io.File.mkdirs(), which has stable JVM-level semantics on Lucee, Adobe CF, and BoxLang. Same pattern as the existing CreateObject("java", "java.lang.System") call already in this file.
  • Applies the identical fix to cli/lucli/services/packages/ManifestCache.cfc, the deliberate mirror called out in the framework file's docstring.

Why the bot's "just drop the second argument" sketch wasn't enough

The wheels-bot triage on the issue suggested either dropping the second arg or branching on engine, claiming "Adobe CF creates parents by default." That's incorrect — Adobe CF's DirectoryCreate(path) only creates the leaf. If ~/.wheels/ doesn't yet exist (fresh install), a single-argument call would still fail on every engine. Java NIO bypasses the BIF entirely and recurses parents the same way everywhere.

Regression coverage

  • vendor/wheels/tests/specs/packages/ManifestCacheEnsureDirSpec.cfc — new spec exercising the multi-level-parent case the original createPath=true was meant to cover, plus the manifests/ subdirectory creation path. Runs on every engine in the compat-matrix.
  • Adds a parallel it() block to the existing cli/lucli/tests/specs/packages/ManifestCacheSpec.cfc so the CLI mirror keeps the same coverage.

Docs

  • .ai/wheels/cross-engine-compatibility.md gains a new entry documenting the BIF gotcha so the next contributor reaches for Java NIO before tripping on it.

Test plan

  • CI compat-matrix passes on Lucee 5/6/7 + Adobe CF 2018/2021/2023/2025 + BoxLang
  • New ManifestCacheEnsureDirSpec is green in the core suite for every engine × DB combo
  • New ManifestCacheSpec.it deep-nested case is green in the CLI suite on Lucee 7
  • Manually verify the Tools → Packages page no longer 500s on Adobe CF (post-merge smoke)

Closes #2567

…orks

ManifestCache.$ensureDir used DirectoryCreate(path, true). The createPath
flag is a Lucee extension — Adobe CF rejects the second argument and
crashes the Tools → Packages page on every fresh ACF install with
"The function takes 1 parameter" (#2567).

Routes the recursive mkdir through java.io.File.mkdirs(), which has stable
JVM-level semantics on Lucee, Adobe CF, and BoxLang. Mirrors the fix in
the CLI-side ManifestCache so the two intentionally-paired files stay
in sync.

Adds regression coverage exercising a deeply-nested cache root (where
parents don't exist yet — the case the createPath flag was meant to
cover) and the on-first-use manifests subdirectory creation path.

Also documents the BIF gotcha in .ai/wheels/cross-engine-compatibility.md
so the next person reaches for Java NIO before tripping on it.

Closes #2567

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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: PR #2598 fixes a real cross-engine crash — DirectoryCreate(path, true) is a Lucee-only extension that Adobe CF rejects with "The function takes 1 parameter" on fresh installs, breaking the Tools → Packages page. The fix routes through java.io.File.mkdirs(), which is a JDK API and behaves identically on all three engines. The implementation is correct, the tests are well-structured BDD specs that cover both the multi-level-parent regression and the manifests/ subdirectory path, and the cross-engine-compatibility doc is updated appropriately. I have two minor findings — one robustness nit and one missing changelog entry — neither is blocking.

Correctness

mkdirs() return value is silently discarded

vendor/wheels/services/packages/ManifestCache.cfc line 129 (and the mirror at cli/lucli/services/packages/ManifestCache.cfc line 123):

CreateObject("java", "java.io.File").init(arguments.path).mkdirs();

java.io.File.mkdirs() returns boolean: true if all directories were created, false if they already existed or if creation failed (e.g. permissions, read-only filesystem). Discarding this silently means a permission failure becomes a confusing FileWrite error a few lines later rather than a clear "could not create cache directory" message.

Suggested improvement (not a hard blocker since the subsequent FileWrite will still throw, but the diagnostic message will be opaque):

var created = CreateObject("java", "java.io.File").init(arguments.path).mkdirs();
if (!created && !DirectoryExists(arguments.path)) {
    Throw(
        type    = "Wheels.Packages.CacheDir",
        message = "Could not create cache directory: " & arguments.path
    );
}

The !DirectoryExists guard handles the case where mkdirs() returns false because the directory already appeared between the initial existence check and the mkdirs() call — that race outcome is harmless.

Docs

Missing CHANGELOG entry

The [Unreleased] → ### Fixed section in CHANGELOG.md (line 155) has entries for recent fixes but nothing for this PR. Other Adobe CF compatibility fixes in the same release have been recorded there. A one-liner is all it needs:

- Tools → Packages page no longer 500s on fresh Adobe CF installs. `$ensureDir` in `ManifestCache`
  called `DirectoryCreate(path, true)` whose `createPath` argument is a Lucee extension rejected by
  Adobe CF. Replaced with `java.io.File.mkdirs()` which recurses parents identically on every engine.
  (#2567)

Tests

Both the framework spec (vendor/wheels/tests/specs/packages/ManifestCacheEnsureDirSpec.cfc) and the CLI addition (cli/lucli/tests/specs/packages/ManifestCacheSpec.cfc lines 74-89) correctly use WheelsTest BDD syntax, cover the regression case (multi-level parents), and use try/finally for temp-dir cleanup. The CreateUUID()-based uniqueness avoids test-run collisions. No concerns here.

Cross-engine

The fix is specifically motivated by cross-engine compatibility and is correct. java.io.File.mkdirs() is a core JDK API present on every JVM-hosted CFML engine (Lucee, Adobe CF, BoxLang). The .ai/wheels/cross-engine-compatibility.md entry documents the pattern clearly and will prevent future contributors from re-introducing the same BIF call. No further cross-engine issues detected.

Commits

fix(packages): use java.io.File.mkdirs() for cache dirs so Adobe CF works — valid type, scope, and subject. Under 100 chars. Not ALL-CAPS. Conforms to commitlint.config.js.

@wheels-bot

wheels-bot Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is technically sound. Both findings are accurate, no false positives were detected, and a re-scan of the diff found no missed issues. A's COMMENTED (non-blocking) verdict is appropriate for a PR whose core fix, tests, and documentation are all correct. Converging on approve.

Sycophancy

None detected. A led with a substantive technical summary, identified two concrete issues with file/line citations, and submitted a COMMENTED state rather than a blanket approval — none of the usual sycophancy signals.

False positives

None detected.

  • mkdirs() return value (finding 1): The actual code at both call sites — CreateObject("java", "java.io.File").init(arguments.path).mkdirs(); — does discard the boolean return. A's analysis is correct: false from mkdirs() can mean either "directory already appeared between the existence check and the call" (harmless) or "creation failed due to permissions/read-only FS" (not harmless). The !DirectoryExists guard in A's suggested fix handles the race correctly. Claim verified against the diff.
  • Missing CHANGELOG entry (finding 2): CHANGELOG.md line 155 is the ### Fixed section of the Unreleased block. Grepping the file confirms there is no entry for issue Packages page fails on Adobe ColdFusion: DIRECTORYCREATE called with unsupported arguments #2567 / ManifestCache.$ensureDir / Adobe CF fresh-install crash. Every other recent Adobe CF compatibility fix in the same block has an entry. A's claim is accurate.

Missed issues

None detected. The diff is tightly scoped — two identical function rewrites, two BDD spec additions, one doc entry. Test files use WheelsTest BDD syntax correctly, cleanup is in try/finally blocks, CreateUUID()-based temp-dir names avoid collision across parallel runs, and the .ai/wheels/cross-engine-compatibility.md addition is accurate and follows the format of existing entries in that file.

Verdict alignment

A's COMMENTED verdict (non-blocking) is consistent with the findings. Both are quality improvements on an otherwise correct PR — neither describes a behavioral defect or a cross-engine regression. Submitting COMMENTED rather than APPROVED is defensible; submitting APPROVED with optional suggestions would also have been fine.

Convergence

Aligned. A's two findings are valid and the PR's core implementation is sound. Neither finding constitutes a blocker. Joint recommendation: approve; the mkdirs error-handling and CHANGELOG entry are worth the author picking up but should not gate merge.

bpamiri added 2 commits May 11, 2026 19:51
…log entry

Address Reviewer A on #2598:

- $ensureDir now captures mkdirs()'s boolean return and throws a typed
  Wheels.Packages.CacheDir exception when creation actually failed.
  Without this guard, a permission/read-only-fs failure surfaced as a
  misleading downstream FileWrite error rather than a clear "could not
  create cache directory" diagnostic. The !DirectoryExists re-check
  swallows the benign concurrent-creation race where mkdirs() returns
  false because another worker won.

- CHANGELOG entry under [Unreleased] → Fixed describing the user-facing
  symptom (Tools → Packages 500 on fresh ACF), root cause, and the
  cross-engine fix, mirroring the format of the surrounding entries.
@bpamiri

bpamiri commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

Both findings addressed in 164cdfcdd:

  • mkdirs() return value: $ensureDir now captures the boolean and throws Wheels.Packages.CacheDir when creation actually failed. The !DirectoryExists re-check swallows the benign concurrent-creation race. Same change mirrored into cli/lucli/services/packages/ManifestCache.cfc.
  • CHANGELOG: entry added under [Unreleased] → Fixed describing the symptom, root cause, and fix.

b1941ab55 merges develop and resolves a parallel CHANGELOG insert from #2597.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor

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 is a follow-up iteration on PR #2598. The previous Reviewer A round (commit 3088309a8) flagged two issues: the mkdirs() boolean return was silently discarded, and no CHANGELOG entry existed. Both have been addressed in the follow-up commit 164cdfcdd. The implementation is now correct, well-documented, and cross-engine safe. Approving.

Correctness

Both previous findings are resolved.

mkdirs() return value captured and checked (vendor/wheels/services/packages/ManifestCache.cfc line 132, CLI mirror at cli/lucli/services/packages/ManifestCache.cfc line 126):

local.created = CreateObject("java", "java.io.File").init(arguments.path).mkdirs();
if (!local.created && !DirectoryExists(arguments.path)) {
    Throw(
        type    = "Wheels.Packages.CacheDir",
        message = "Could not create cache directory '#arguments.path#'."
    );
}

The !DirectoryExists re-check correctly handles the benign race where mkdirs() returns false because another thread created the directory concurrently — that outcome is not a failure. A genuine permission/read-only-fs failure now throws Wheels.Packages.CacheDir immediately rather than producing an opaque downstream FileWrite error. The typed exception makes it catchable at call sites if needed.

Cross-engine

No concerns. java.io.File.mkdirs() is a JDK API available on Lucee, Adobe CF, and BoxLang identically. No Lucee-only BIFs remain in the $ensureDir path.

Tests

vendor/wheels/tests/specs/packages/ManifestCacheEnsureDirSpec.cfc extends wheels.WheelsTest and covers two distinct cases:

  • Multi-level parent creation (the original createPath=true regression case).
  • manifests/ subdirectory creation on first writeManifest call.

Both use try/finally for cleanup, CreateUUID()-based uniqueness to avoid test collisions, and correct BDD syntax. The CLI parallel case in ManifestCacheSpec.cfc lines 74-89 mirrors the same scenario with a simpler path-cleanup strategy (saving unique before constructing the nested path, which is marginally cleaner than the framework spec's ListFirst(Replace(...)) approach — worth harmonising in a follow-up, but not a blocker).

Docs

CHANGELOG (CHANGELOG.md line 158): Entry is present, placed correctly under [Unreleased] → Fixed, describes the user-visible symptom (Tools → Packages 500), root cause, and fix. Matches the verbosity and style of surrounding entries.

.ai cross-engine doc (.ai/wheels/cross-engine-compatibility.md lines 128-142): The new entry clearly flags DirectoryCreate(path, true) as a Lucee-only extension, shows the wrong and right patterns, and explains why Java NIO is the correct choice. Future contributors will hit this before tripping on the BIF.

Commits

Both commits conform to commitlint.config.js:

  • fix(packages): use java.io.File.mkdirs() for cache dirs so Adobe CF works — valid type, scope, subject under 100 chars.
  • fix(packages): throw typed exception when mkdirs() fails + add changelog entry — same. The body explains the "why" clearly (diagnostic clarity on permission failures, race-condition guard reasoning).

@wheels-bot

wheels-bot Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Wheels Bot — Reviewer B (round 1)

A's review is accurate and its APPROVED verdict is well-earned. Both findings from the previous round were addressed in the follow-up commits; A verified each one against the actual diff lines. No sycophancy, one very minor doc observation that A omitted, but it does not affect verdict alignment.

Sycophancy

None detected. A opened with a concrete summary of the previous round's state, cited specific file paths and line numbers for both verified findings, and submitted APPROVED only after confirming the fixes rather than on the basis of the PR description alone.

False positives

None detected.

  • mkdirs() fix (lines cited by A): The implementation at both call sites matches A's description. local.created captures the return value; the !local.created && !DirectoryExists guard correctly handles the concurrent-creation race before throwing Wheels.Packages.CacheDir. Verified against the diff.
  • CHANGELOG placement and content: The hunk confirms the entry lands in the [Unreleased] -> Fixed block. A's characterisation of its content is accurate.
  • Test claims: Both spec files use WheelsTest BDD syntax, try/finally cleanup, and CreateUUID()-based names. A's observation about the cleanup style difference between the framework spec (ListFirst(Replace(...))) and the CLI spec (pre-captured unique) is accurate and correctly labelled non-blocking.

Missed issues

One minor doc gap, not a blocker:

  • .ai/wheels/cross-engine-compatibility.md RIGHT example omits error handling. The RIGHT code block shows CreateObject("java", "java.io.File").init(path).mkdirs() without capturing the boolean or throwing on failure, while the actual implementation does both. A contributor copying the doc pattern verbatim would get the portability fix right but miss the error handling. The doc's purpose is demonstrating the engine-portability pattern, so this does not affect correctness of the PR; worth a one-line follow-up note but does not gate merge.

Verdict alignment

A's APPROVED verdict is consistent with the findings. Both prior actionable items are addressed; the remaining observation (doc example completeness) is non-blocking. Approval is appropriate.

Convergence

Aligned. A's review is clean: no unresolved missed issues, no false-positive disputes, and APPROVED is the correct verdict for a follow-up iteration that addressed the consensus findings from the previous round. Joint recommendation: approve and merge.

@bpamiri
bpamiri merged commit 3be4f9c into develop May 12, 2026
6 of 11 checks passed
@bpamiri
bpamiri deleted the claude/angry-franklin-55d865 branch May 12, 2026 03:01
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.

Packages page fails on Adobe ColdFusion: DIRECTORYCREATE called with unsupported arguments

1 participant