test(cli): cover every shipped onApplicationEnd handler for #3379 - #3381
Conversation
…rvive Adobe teardown On Adobe ColdFusion 2023 the live application scope is unreliable during applicationStop() teardown, so bare application.wo could resolve to a stale Java String[] and throw "Element wo is undefined in a Java object of type class [Ljava.lang.String;", erroring the whole site until a CF service restart. onApplicationEnd() now invokes the Wheels global through the passed-in arguments.applicationScope.wo, guarded with StructKeyExists, so a partially reclaimed scope degrades to a no-op. Applied to the CLI app template, the demo app, and the bundled example apps. Refs #3379 Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
…Adobe CF Add cross-engine invariant #19 to CLAUDE.md and a matching section to .ai/wheels/cross-engine-compatibility.md: during applicationStop() teardown on Adobe CF 2023, bare application.wo can resolve to a stale Java String[] and throw 'Element wo is undefined...'. Route onApplicationEnd() through arguments.applicationScope.wo instead and guard with StructKeyExists (#3379, fixed in #3380). Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
…scope guard The #3380 structural spec only scanned the CLI template and demo Application.cfc. The same teardown guard was applied to the bundled example apps, so a revert there would have gone uncaught. Extend the targets list and add a discovery check over the shipped trees so a newly added onApplicationEnd copy cannot slip the list. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR — This PR extends the #3379 structural guard so OnApplicationEndScopeGuardSpec now asserts the Adobe-CF teardown fix across every shipped Application.cfc that declares onApplicationEnd (CLI template, demo app, and both bundled example apps), plus a discovery it() that fails if a new shipped copy ever slips the targets list. It closes the coverage gap left by the bot draft #3380, whose framework/docs changes are carried along in the base commits. The diff is clean, mirrors established prior art, and I verified the discovery logic against the actual repo tree. Verdict: approve.
Correctness
No issues. I confirmed the discovery it() will pass rather than false-fail:
- The four listed
targetsare exactly the shippedApplication.cfcfiles that declareonApplicationEnd. The siblingpublic/miscellaneous/Application.cfcstubs under each root exist but declare noonApplicationEndhandler, so$discoverShippedOnApplicationEndHandlersskips them (grep 'function onApplicationEnd'over themiscellaneous/Application.cfcfiles returns no matches). The discovered set therefore equals the targets set, matching the PR's "5 pass" claim. - The manual brace-matching body extractor and the per-file assertions (no
application.wo., must route througharguments.applicationScope.wo, guard-before-deref ordering viaguardPos < derefPos) are copied verbatim from the passingOnErrorFallbackGuardSpec, so the shape is trusted prior art.
Cross-engine
No issues found; I checked the new discovery helper specifically:
vendor/wheels/tests/specs/cli/OnApplicationEndScopeGuardSpec.cfc:168—Left(rootNormalized, Len(rootNormalized) - 1)is guarded byLen(rootNormalized) > 1at line 167, so the length argument is never0and this avoids the Lucee 7Left(str, 0)crash (CLAUDE.md cross-engine invariant #8).- Loop-variable capture for the per-file specs uses the
(function(relPath){ ... })(rel)IIFE form (matchingOnErrorFallbackGuardSpec), so eachit()binds its own path rather than the final iteration's. - No compile hazards for the all-engine
directory="wheels.tests.specs"sweep: no(new X()).method()receiver (invariant 16a), no zero-arg call throughapplicationscope in a closure (16b), no inline closure as a constructor named arg (invariant 5).reFindNoCase(..., content, 1, true)uses the standard 4-arg struct form. - Private
$-prefixed helpers called by bare name from inside theit()closures is fine here — this is a directly-instantiated spec, not a$integrateComponents()-mixed model/controller, and it mirrors$stripCfmlCommentsusage in the sibling spec.
Docs
CLAUDE.md (invariant #19) and .ai/wheels/cross-engine-compatibility.md carry the teardown-hazard write-up, and changelog.d/3379-onapplicationend-scope-guard.fixed.md uses the correct fragment form (<slug>.fixed.md) rather than a direct CHANGELOG.md [Unreleased] edit. All consistent with the code. (These landed on the base #3380 commits and appear in the diff because the PR is based on develop.)
Commits
All three conform to commitlint.config.js: test(cli): …, docs: …, fix(config): … — valid types, subjects <= 100 chars, not ALL-CAPS, DCO signed-off.
Nice touch: the discovery walk turns a passive "did someone remember to add the new file?" into an enforced invariant, which is exactly the failure mode that motivated widening the spec.
Summary
Human follow-up on the wheels-bot draft #3380. Keeps the bot's
onApplicationEnd()guard as-is and closes the review gap: the structural spec now scans every shippedApplication.cfcthat declaresonApplicationEnd, not just the CLI template and demo app.This branch is based on
fix/bot-3379-element-wo-is-undefined-in-a-java-object-of-type-c@e9699f473and includes those bot commits plus the spec coverage change. Merging this PR is sufficient; leave #3380 open as draft unless a maintainer prefers to close it as superseded.Related Issue
Refs #3379
Improves #3380 (does not replace the bot draft unless this PR is the one merged).
What is actually broken
On Adobe ColdFusion 2023,
public/Application.cfconApplicationEnd()fires synchronously duringapplicationStop()teardown. Bareapplication.wocan resolve against a torn-down scope / JavaString[]and throwElement wo is undefined in a Java object of type class [Ljava.lang.String;. The captured stack is the recovery path (onRequestStart→$simpleLock→$handleRestartAppRequest→applicationStop()→onApplicationEnd). Overnight idle reclaim is believed to call the same handler the same way. The site stays dead until a CF service restart;?reload=truedoes not recover.Existing 4.0.5 apps must apply the same edit to their own
public/Application.cfc. That is expected — this change cannot patch live user apps from the vendor tree.What was verified
arguments.applicationScope.woand short-circuits withStructKeyExistsonwo,wheels, andwheels.eventPath. That matches the$wheelsBrowserLaunchercleanup already in the same handler (the only dependable reference at shutdown).application.woandarguments.applicationScope.woare the same instance.Application.cfcstill has the unguardedapplication.wodereference insideonApplicationEnd. Test-only copies (vendor/wheels/tests,rocketunit_tests,cli/lucli/tests) and emptymiscellaneous/Application.cfcstubs do not declare the handler.Spec coverage change
vendor/wheels/tests/specs/cli/OnApplicationEndScopeGuardSpec.cfcnow asserts the same three source-shape checks on:cli/lucli/templates/app/public/Application.cfcpublic/Application.cfcexamples/starter-app/public/Application.cfc(was patched, previously untested)examples/tweet/public/Application.cfc(was patched, previously untested)A discovery
it()walkscli/lucli/templates,public/, andexamples/and fails if any shippedApplication.cfcthat declaresonApplicationEndis missing from that list, so the coverage gap cannot recur.The per-file assertions are unchanged (no live
application.wo.*, route througharguments.applicationScope.wo,StructKeyExistsbefore the dereference). Failing → passing structural-spec pattern is preserved.Type of Change
Feature Completeness Checklist
Signed-off-by:(git commit -s)onApplicationEnd().ai/wheels/cross-engine-compatibility.md)changelog.d/3379-onapplicationend-scope-guard.fixed.md(unchanged; description still accurate)wheels.tests.specs.clion this SHA: 23 bundles, 145 pass / 0 fail / 0 error.OnApplicationEndScopeGuardSpec: 5 pass (1 discovery + 4 handlers). CILucee 7 + SQLite (LuCLI)also green.Test Plan
The failure only manifests on Adobe CF during real
applicationStop()teardown, which cannot be reproduced inside a spec without killing the runner. Local verification of the structural guard:curl -sL "http://localhost:8080/wheels/core/tests?db=sqlite&directory=wheels.tests.specs.cli&format=json"Result on this SHA: 145 pass, 0 fail, 0 error, 23 bundles. The new spec reported 5 passing examples.
CI that must go green
These are green on this SHA:
cursor/branch; the structural spec pattern is unchanged)Smoke: testing (Lucee 7)— this timed out at 20m on the bot SHA (e9699f473); green hereSmoke: production (Lucee 7)Smoke: testing (Adobe 2023)andSmoke: production (Adobe 2023)Lucee 7 + SQLite (LuCLI)verifymay still be running (docs check; not a smoke). No full engine × DB matrix. Leave approve-on-develop to Peter. Do not merge from this PR.Screenshots / Output
N/A — structural spec + existing handler guard.