You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
onError masks every app-start exception in dev: $getRequestTimeout() reads application.wheels.engineAdapter after $hasEngineAdapter() matched application.$wheels #3076
When any exception is thrown during application start (settings/routes load), the development error page does not show the original error. Instead onError itself crashes with key [ENGINEADAPTER] doesn't exist (Lucee 7) / Element WHEELS.ENGINEADAPTER is undefined (Adobe 2023) at vendor/wheels/Global.cfc:2518, hiding the real failure. Reproduced identically on Lucee 7.0.1.100 and Adobe 2023.0.11 (develop @ 840274b).
Concrete victim: the Cors guard Throw(type="Wheels.Cors.InvalidConfiguration") (vendor/wheels/middleware/Cors.cfc:30-37) exists precisely to "fail fast with a clear message", and the CORS guide promises that error type at app start — the user never sees it (Wheels.Cors.InvalidConfiguration appears 0 times in the rendered page, grep-verified). The masking applies to EVERY app-start exception type, not just this one.
Any request → 500 whose page is the secondary ENGINEADAPTER error from onError (public/Application.cfc:397 → application.wo.$getRequestTimeout()).
Root cause
Global.cfc::$hasEngineAdapter() returns true if the adapter exists in application.wheels OR application.$wheels (the startup staging struct). $getRequestTimeout() then unconditionally reads application.wheels.engineAdapter:
if ($hasEngineAdapter()) {
return application.wheels.engineAdapter.getRequestTimeout(); // line 2518 — wrong scope when only $wheels matched
}
During a failed startup only the $wheels branch matches (on Adobe, application.wheels wasn't even a struct), so the read throws inside onError and replaces the original exception.
Proposed direction
In $getRequestTimeout(), resolve the adapter from whichever scope actually has it (or wrap the read in the existing engine-native fallback the surrounding code already provides for the "error during startup" case). Audit other $hasEngineAdapter() consumers for the same two-branch-check / one-branch-read mismatch.
Acceptance
A deliberate app-start throw (e.g. the Cors misconfiguration above) renders ITS OWN error type/message on the dev error page on Lucee and Adobe.
Spec or harness probe covering the failed-startup onError path.
Related but distinct from #3061 (onError DI-container wipe) — same onError code path, different bug.
Reported by the guide-behavioral-audit P1 batch 2 (work item p1-12-cors, claim cors-12; live on both engines, develop @ 840274b).
The report describes observable wrong behavior with a clear "what happened / what I expected": any app-start exception (e.g. the Wheels.Cors.InvalidConfiguration guard) is masked because onError itself crashes with key [ENGINEADAPTER] doesn't exist (Lucee 7) / Element WHEELS.ENGINEADAPTER is undefined (Adobe 2023), hiding the original failure. Reproduced on both engines at develop @ 840274b2d.
Suspected layer
config — app-start / request-lifecycle error handling. The defect is in framework core (vendor/wheels/Global.cfc), reached via the onError path in public/Application.cfc. See .ai/wheels/troubleshooting/ and .ai/wheels/cross-engine-compatibility.md for grounding (there is no dedicated config layer doc).
Fix sketch
The root cause is confirmed at vendor/wheels/Global.cfc:2516-2528. $getRequestTimeout() gates on $hasEngineAdapter(), which returns true if the adapter exists in eitherapplication.wheelsor the application.$wheels startup staging struct — but then unconditionally reads application.wheels.engineAdapter (line 2518), which throws when only the $wheels branch matched (the failed-startup case). The framework already ships the correct resolver immediately below: $engineAdapter() (line 2534) checks both scopes and returns whichever holds the adapter. The mechanical fix is to replace the bare application.wheels.engineAdapter.getRequestTimeout() read with $engineAdapter().getRequestTimeout() (wrapped so a throw still falls through to the existing engine-native fallback at lines 2521-2527). Per the issue's proposed direction, also audit other $hasEngineAdapter()-gated consumers in Global.cfc for the same two-branch-check / one-branch-read mismatch.
Confidence: medium
The report is exceptionally clear and the fix is nearly mechanical (an existing helper already does the right thing), but the auto-downgrade rule fires: this is an explicit cross-engine concern (Lucee 7 vs Adobe 2023 differ — on Adobe application.wheels was not even a struct), so confidence is capped at medium.
Spec: vendor/wheels/tests/specs/global/engineAdapterScopedReadersSpec.cfc — failing → passing. It simulates the failed-startup scope mismatch (adapter staged only in application.$wheels) and, pre-fix, reproduces the exact masking error key [ENGINEADAPTER] doesn't exist for all three affected readers.
Implementation: vendor/wheels/Global.cfc — $getRequestTimeout() (the reported line), plus $statusCode() and $contentType() found by the requested audit, now resolve the adapter through the existing two-scope $engineAdapter() helper instead of reading application.wheels.engineAdapter unconditionally. Happy-path behavior is unchanged; the failed-startup path now surfaces the real exception (e.g. Wheels.Cors.InvalidConfiguration).
Verified on Lucee 7.0.1.100 + SQLite: the three specs go failing → passing and the full core suite stays green (4424 pass, 0 fail, 0 error). The fix is engine-agnostic, so the matching Adobe failure is resolved the same way; the CI matrix will confirm Adobe/BoxLang.
A human review is required before merge. The Reviewer will weigh in shortly. Doc updates (MDX guides, .ai/wheels/, CLAUDE.md) are handled separately by bot-update-docs.yml.
Activity
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Triage
Classified as bug.
The report describes observable wrong behavior with a clear "what happened / what I expected": any app-start exception (e.g. the
Wheels.Cors.InvalidConfigurationguard) is masked becauseonErroritself crashes withkey [ENGINEADAPTER] doesn't exist(Lucee 7) /Element WHEELS.ENGINEADAPTER is undefined(Adobe 2023), hiding the original failure. Reproduced on both engines atdevelop @ 840274b2d.Suspected layer
config— app-start / request-lifecycle error handling. The defect is in framework core (vendor/wheels/Global.cfc), reached via theonErrorpath inpublic/Application.cfc. See.ai/wheels/troubleshooting/and.ai/wheels/cross-engine-compatibility.mdfor grounding (there is no dedicatedconfiglayer doc).Fix sketch
The root cause is confirmed at
vendor/wheels/Global.cfc:2516-2528.$getRequestTimeout()gates on$hasEngineAdapter(), which returns true if the adapter exists in eitherapplication.wheelsor theapplication.$wheelsstartup staging struct — but then unconditionally readsapplication.wheels.engineAdapter(line 2518), which throws when only the$wheelsbranch matched (the failed-startup case). The framework already ships the correct resolver immediately below:$engineAdapter()(line 2534) checks both scopes and returns whichever holds the adapter. The mechanical fix is to replace the bareapplication.wheels.engineAdapter.getRequestTimeout()read with$engineAdapter().getRequestTimeout()(wrapped so a throw still falls through to the existing engine-native fallback at lines 2521-2527). Per the issue's proposed direction, also audit other$hasEngineAdapter()-gated consumers inGlobal.cfcfor the same two-branch-check / one-branch-read mismatch.Confidence: medium
The report is exceptionally clear and the fix is nearly mechanical (an existing helper already does the right thing), but the auto-downgrade rule fires: this is an explicit cross-engine concern (Lucee 7 vs Adobe 2023 differ — on Adobe
application.wheelswas not even a struct), so confidence is capped atmedium.<!-- wheels-bot:triage:3076 -->
<!-- wheels-bot:triage-class:bug -->
<!-- wheels-bot:triage-confidence:medium -->
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Fix proposed
Draft PR: #3108
Spec:
vendor/wheels/tests/specs/global/engineAdapterScopedReadersSpec.cfc— failing → passing. It simulates the failed-startup scope mismatch (adapter staged only inapplication.$wheels) and, pre-fix, reproduces the exact masking errorkey [ENGINEADAPTER] doesn't existfor all three affected readers.Implementation:
vendor/wheels/Global.cfc—$getRequestTimeout()(the reported line), plus$statusCode()and$contentType()found by the requested audit, now resolve the adapter through the existing two-scope$engineAdapter()helper instead of readingapplication.wheels.engineAdapterunconditionally. Happy-path behavior is unchanged; the failed-startup path now surfaces the real exception (e.g.Wheels.Cors.InvalidConfiguration).Verified on Lucee 7.0.1.100 + SQLite: the three specs go failing → passing and the full core suite stays green (4424 pass, 0 fail, 0 error). The fix is engine-agnostic, so the matching Adobe failure is resolved the same way; the CI matrix will confirm Adobe/BoxLang.
A human review is required before merge. The Reviewer will weigh in shortly. Doc updates (MDX guides,
.ai/wheels/,CLAUDE.md) are handled separately bybot-update-docs.yml.docs: add [ENGINEADAPTER] startup-masking error entry to common-errors