Skip to content

fix(config): route onApplicationEnd through applicationScope.wo to survive Adobe teardown - #3380

Merged
2 commits merged into
developfrom
fix/bot-3379-element-wo-is-undefined-in-a-java-object-of-type-c
Aug 20, 2026
Merged

fix(config): route onApplicationEnd through applicationScope.wo to survive Adobe teardown#3380
2 commits merged into
developfrom
fix/bot-3379-element-wo-is-undefined-in-a-java-object-of-type-c

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

On Adobe ColdFusion 2023 the framework's onApplicationEnd() handler fires synchronously during applicationStop() teardown. Inside that teardown the live application scope is no longer reliable — bare application.wo can resolve against a stale/torn-down scope and land on a Java String[], throwing Element wo is undefined in a Java object of type class [Ljava.lang.String; and erroring the whole site until a CF service restart. This routes the onapplicationend.cfm include through the passed-in arguments.applicationScope.wo (the only dependable reference at shutdown, already used by the $wheelsBrowserLauncher cleanup in the same handler) and guards it with StructKeyExists so a partially reclaimed scope degrades to a no-op instead of a hard error. The same edit is applied to the CLI wheels new template, the repo's demo app, and the two bundled example apps; existing apps should apply the same change to their public/Application.cfc.

Related Issue

Refs #3379

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-off -- commit carries Signed-off-by: (git commit -s)
  • Tests -- vendor/wheels/tests/specs/cli/OnApplicationEndScopeGuardSpec.cfc (structural guard, failing → passing)
  • Framework Docs -- left for bot-update-docs.yml
  • AI Reference Docs -- left for bot-update-docs.yml
  • CLAUDE.md -- left for bot-update-docs.yml
  • Changelog fragment -- changelog.d/3379-onapplicationend-scope-guard.fixed.md
  • Test runner passes -- wheels.tests.specs.cli scope green (23 bundles, 0 fail / 0 error; the new spec: 2 pass)

Test Plan

The failure only manifests on Adobe CF during real applicationStop() teardown, which cannot be reproduced inside a spec without killing the test runner. The guard is therefore structural: OnApplicationEndScopeGuardSpec scans every Application.cfc that ships the handler (the CLI template and the repo demo app) and asserts onApplicationEnd() (1) never dereferences the live application.wo, (2) routes through arguments.applicationScope.wo, and (3) guards it with StructKeyExists(arguments.applicationScope, "wo") before the dereference. Verified failing before the fix and passing after via:

curl -sL "http://localhost:60007/wheels/core/tests?db=sqlite&directory=wheels.tests.specs.cli&format=json"

Mirrors the existing OnErrorFallbackGuardSpec (issue #2773) pattern.

…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>
@wheels-bot

wheels-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • CLAUDE.md — new cross-engine invariant Namespaced controllers #19: onApplicationEnd() must route through arguments.applicationScope.wo (not bare application.wo) on Adobe CF 2023, with a StructKeyExists guard; existing apps must apply this to their public/Application.cfc
  • .ai/wheels/cross-engine-compatibility.md — new section "Application Scope Unreliable During onApplicationEnd() Teardown" with wrong/right code examples and the note for existing apps

No MDX user guide page was updated: no existing v4-0-0 guide page covers onApplicationEnd() lifecycle detail, and the instructions prohibit creating new pages.

@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

TL;DR — This PR hardens onApplicationEnd() against Adobe CF 2023 applicationStop() teardown, where the live application scope can resolve application.wo to a stale Java String[] and throw Element wo is undefined in a Java object of type class [Ljava.lang.String;. It routes the onapplicationend.cfm include through the dependable arguments.applicationScope.wo (already used for the $wheelsBrowserLauncher cleanup in the same handler) and guards it with StructKeyExists. The change is correct, cross-engine safe, and mirrors an established prior-art pattern. Verdict: comment — the fix is clean; one minor test-coverage observation below, nothing blocking.

Tests

Solid structural guard. vendor/wheels/tests/specs/cli/OnApplicationEndScopeGuardSpec.cfc faithfully mirrors OnErrorFallbackGuardSpec (#2773) — same brace-matched body extraction, same $stripCfmlComments (CLAUDE.md anti-pattern #14), same private-helper-called-from-it()-closure shape that already ships and passes on every engine. Given the failure only manifests during real Adobe teardown (uncatchable in a spec without killing the runner), a source-shape guard is the right call.

Minor: the guard patches four files but only asserts two.

var targets = [
    "cli/lucli/templates/app/public/Application.cfc",
    "public/Application.cfc"
];

examples/starter-app/public/Application.cfc and examples/tweet/public/Application.cfc receive the identical fix but aren't covered, so a future revert of those two would go uncaught. This is consistent with the mirrored OnErrorFallbackGuardSpec (which also targets only these two) and the PR body scopes the guard to "the CLI template and the repo's own demo app," so it is deliberate — noting only in case extending targets to the example apps is cheap and worthwhile. Not blocking.

Conventions

No issues. The guard checks wo, wheels, and wheels.eventPath before the dereference; && short-circuits, so arguments.applicationScope.wheels.eventPath is only touched once wheels is confirmed present. Happy-path behavior is unchanged — in normal operation application.wo and arguments.applicationScope.wo are the same instance. Changelog fragment changelog.d/3379-onapplicationend-scope-guard.fixed.md uses a valid .fixed type with a complete bullet. Refs #3379 (not Closes) is the right call for a defensive/partial fix.

Cross-engine

No issues. arguments.applicationScope.wo.$include(...) is a method call on a passed-in plain struct + CFC instance, not a function member on the live application scope (invariant #2 does not apply), and it is application code rather than a compiled spec closure (invariant #16b does not apply). The struct ApplicationScope (capital) parameter vs arguments.applicationScope (lower) access is fine — CFML argument names are case-insensitive on all engines.

Commits

Conforms. fix(config): route onApplicationEnd through applicationScope.wo to survive Adobe teardown — valid type/scope, subject well under 100 chars, not ALL-CAPS, body explains the "why," DCO Signed-off-by present.

@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

TL;DR — This PR hardens onApplicationEnd() against Adobe CF 2023 applicationStop() teardown, where the live application scope can resolve application.wo 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. It routes the onapplicationend.cfm include through the passed-in arguments.applicationScope.wo (the same reference the $wheelsBrowserLauncher cleanup in this handler already uses) and guards it with StructKeyExists. The change is correct, precisely scoped, cross-engine safe, and backed by a structural regression guard. Verdict: comment — the fix is clean; one minor, deliberate test-scoping observation below, nothing blocking.

Correctness

No issues. The fix is scoped exactly to the one handler that fires during teardown. Every other application.wo.* dereference (onApplicationStart, onRequestStart, onRequestEnd, onAbort, onError) fires during the normal request lifecycle where the live scope is reliable, so leaving them untouched is correct — git grep "application\.wo" confirms the only occurrences inside onApplicationEnd() are now comments (e.g. public/Application.cfc:161).

The guard short-circuits cleanly with &&:

if (
    StructKeyExists(arguments.applicationScope, "wo")
    && StructKeyExists(arguments.applicationScope, "wheels")
    && StructKeyExists(arguments.applicationScope.wheels, "eventPath")
) {

so arguments.applicationScope.wheels.eventPath (interpolated into the include template) is only touched once wheels is confirmed present. Happy-path behavior is unchanged — in normal operation application.wo and arguments.applicationScope.wo are the same instance.

Cross-engine

No issues. arguments.applicationScope.wo.$include(...) is a method call on a passed-in plain struct + CFC instance, not a function member on the live application scope (invariant #2 N/A), and it is application code with a non-empty argument list, not a zero-arg call in a compiled spec closure (invariant #16b N/A). The struct ApplicationScope (capital) parameter vs arguments.applicationScope (lower) access is fine — CFML argument names are case-insensitive on all engines. The new spec IIFE loop-capture ((function(relPath){...})(rel)), brace-matched body extraction, and bare-called private $stripCfmlComments are byte-for-byte the shipped OnErrorFallbackGuardSpec.cfc (#2773), which already compiles and passes on every engine in the cli scope — so the spec itself is proven cross-engine safe including Adobe.

Tests

Solid structural guard. vendor/wheels/tests/specs/cli/OnApplicationEndScopeGuardSpec.cfc asserts the source shape across the shipping Application.cfc files: (1) no live application.wo.* dereference in onApplicationEnd(), (2) the include routes through arguments.applicationScope.wo, (3) it is guarded by StructKeyExists(arguments.applicationScope, "wo") before the dereference. It strips comments first ($stripCfmlComments, CLAUDE.md anti-pattern #14), so the comment mention of application.wo on line 161 does not trip check (1). Since the failure only manifests during real Adobe teardown (uncatchable in a spec without killing the runner), a source-shape guard is the right call.

Minor (non-blocking): the fix patches four files but the guard only asserts two —

var targets = [
    "cli/lucli/templates/app/public/Application.cfc",
    "public/Application.cfc"
];

examples/starter-app/public/Application.cfc and examples/tweet/public/Application.cfc receive the identical fix but are not covered, so a future revert of those two would go uncaught. This is consistent with the mirrored OnErrorFallbackGuardSpec (same two targets) and the PR body scopes the guard to "the CLI template and the repo demo app," so it is deliberate — noting only in case extending targets to the example apps is cheap and worthwhile.

Docs

Complete. CLAUDE.md gains cross-engine invariant #19, .ai/wheels/cross-engine-compatibility.md gains a matching section, and changelog.d/3379-onapplicationend-scope-guard.fixed.md is a fragment (not a direct [Unreleased] edit) with a valid .fixed type and a complete bullet. Refs #3379 (not Closes) is the right call for a defensive/partial fix.

Commits

Conform. fix(config): route onApplicationEnd through applicationScope.wo to survive Adobe teardown and docs: document onApplicationEnd application scope teardown hazard on Adobe CF — both valid types (fix, docs), subjects well under 100 chars, not ALL-CAPS, bodies explain the "why."

@bpamiri bpamiri closed this pull request by merging all changes into develop in 2d2f9a5 Aug 20, 2026
@bpamiri
bpamiri deleted the fix/bot-3379-element-wo-is-undefined-in-a-java-object-of-type-c branch August 20, 2026 01:19
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.

1 participant