Summary
Hitting a URL with ?reload=true does NOT re-evaluate <cfinclude> directives in app/global/functions.cfm (or any included file like app/global/datapai/helpers.cfm). New functions added to those included files are invisible until a full applicationStop() runs — which is only triggered by ?reload=true&password=<admin> (where <admin> is the admin.password value from lucee.json).
Net effect: when developing new helper functions, the obvious developer reflex (?reload=true) lies. The page renders without error, but the new helper is still undefined. Looks like Lucee/Wheels just isn't picking it up, but actually the reload silently skipped the re-include step.
Repro
- Add a new function to a project's global helpers:
// app/global/datapai/helpers.cfm (the file is included from app/global/functions.cfm)
<cfscript>
function newHelperJustAdded() {
return "I am brand new";
}
</cfscript>
- Hit the dev server with
?reload=true:
curl -s "http://localhost:60050/?reload=true" -o /dev/null -w "HTTP %{http_code}\n"
# HTTP 302
- Try to use the new helper in a controller or view:
// app/controllers/Smoke.cfc
function index() {
writeOutput(newHelperJustAdded()); // throws: function 'newHelperJustAdded' is not defined
abort;
}
- Now hit with
?reload=true&password=titan4dev (or whatever your Lucee admin password is, from lucee.json):
curl -s "http://localhost:60050/?reload=true&password=titan4dev" -o /dev/null -w "HTTP %{http_code}\n"
# HTTP 302
- Re-try step 3 — the helper now resolves correctly.
The difference: ?reload=true&password=... triggers applicationStop(), forcing the application scope to be torn down and onApplicationStart to re-fire, which re-evaluates the app/global/functions.cfm include chain. Bare ?reload=true runs a softer reload that re-loads SOME settings/routes but doesn't re-include global function files.
Impact
This caused real iteration friction during the DataPAI Phase 0 build. Every time we added a new helper function and tried to test it, the first ?reload=true request would appear successful but the function was missing. Spending several minutes debugging "why isn't my helper working" before remembering to use the password form is a common pattern.
Documentation in the dev guides mentions ?reload=true as the standard reload mechanism but doesn't flag this limitation. Users discover it through frustration.
Why this happens (my hypothesis)
Wheels' ?reload=true handling (in vendor/wheels/events/onapplicationstart.cfc and the request lifecycle) re-runs a subset of init code — it re-loads application.wheels config, re-fires routes.cfm to rebuild the route table, etc. But it does NOT re-evaluate app/global/functions.cfm, which is <cfinclude>'d into Global.cfc at the time Global.cfc itself was first instantiated. Including a CFM file at component construction time is a one-shot operation; the included symbols are merged into the component's variables scope and stay there until the component is re-instantiated.
A ?reload=true doesn't tear down application.wo (the Global.cfc instance), so the included symbols remain stale.
?reload=true&password=<admin> calls Lucee's applicationStop() (gated by the admin password as a safety mechanism). This tears down the application scope completely, forcing onApplicationStart to re-fire which re-instantiates application.wo (Global.cfc) which re-evaluates the <cfinclude> chain.
Suggested fixes (any of)
-
Make ?reload=true actually re-include app/global/*.cfm by either (a) re-instantiating Global.cfc as part of the reload pipeline, or (b) tracking the included files and re-evaluating them explicitly. Either way, the behavior matches the developer's mental model: "reload means everything in the app is fresh".
-
Document the limitation prominently in guides.wheels.dev/v4-0-0/configuration-and-defaults/. Show side-by-side what ?reload=true does vs. ?reload=true&password=<admin> so users know which one to reach for.
-
Print a warning during dev runs when a request notices the app/global/*.cfm files have a newer mtime than the application's load time. Something like "Your app/global files have changed since startup; run ?reload=true&password=<admin> for a full reload." Only in dev, only once per file-change-detection.
-
Add a wheels reload CLI command that hits the password-reload URL with the password from lucee.json. Skips the muscle-memory issue entirely.
Repo / version
- Wheels Core:
4.0.0-SNAPSHOT+1779
- File:
vendor/wheels/events/onapplicationstart.cfc (where the reload-with-password branching lives — line ~417 is the $location redirect post-reload, but the reload-decision logic is earlier in the file)
Where found
Surfaced repeatedly during the DataPAI Phase 0 build in Titan. We added ~10 helpers across 2 helper-include files; each helper's first invocation required learning (or remembering) to use the password reload. The CLAUDE.md gotcha list captured at the end of Phase 0 (paiindustries/titan PR #3337 commit e3ab806bb) flags this as one of 9 things to know about Wheels' dev iteration model.
Summary
Hitting a URL with
?reload=truedoes NOT re-evaluate<cfinclude>directives inapp/global/functions.cfm(or any included file likeapp/global/datapai/helpers.cfm). New functions added to those included files are invisible until a fullapplicationStop()runs — which is only triggered by?reload=true&password=<admin>(where<admin>is theadmin.passwordvalue fromlucee.json).Net effect: when developing new helper functions, the obvious developer reflex (
?reload=true) lies. The page renders without error, but the new helper is still undefined. Looks like Lucee/Wheels just isn't picking it up, but actually the reload silently skipped the re-include step.Repro
?reload=true:// app/controllers/Smoke.cfc function index() { writeOutput(newHelperJustAdded()); // throws: function 'newHelperJustAdded' is not defined abort; }?reload=true&password=titan4dev(or whatever your Lucee admin password is, fromlucee.json):The difference:
?reload=true&password=...triggersapplicationStop(), forcing the application scope to be torn down andonApplicationStartto re-fire, which re-evaluates theapp/global/functions.cfminclude chain. Bare?reload=trueruns a softer reload that re-loads SOME settings/routes but doesn't re-include global function files.Impact
This caused real iteration friction during the DataPAI Phase 0 build. Every time we added a new helper function and tried to test it, the first
?reload=truerequest would appear successful but the function was missing. Spending several minutes debugging "why isn't my helper working" before remembering to use the password form is a common pattern.Documentation in the dev guides mentions
?reload=trueas the standard reload mechanism but doesn't flag this limitation. Users discover it through frustration.Why this happens (my hypothesis)
Wheels'
?reload=truehandling (invendor/wheels/events/onapplicationstart.cfcand the request lifecycle) re-runs a subset of init code — it re-loadsapplication.wheelsconfig, re-firesroutes.cfmto rebuild the route table, etc. But it does NOT re-evaluateapp/global/functions.cfm, which is<cfinclude>'d into Global.cfc at the time Global.cfc itself was first instantiated. Including a CFM file at component construction time is a one-shot operation; the included symbols are merged into the component's variables scope and stay there until the component is re-instantiated.A
?reload=truedoesn't tear downapplication.wo(the Global.cfc instance), so the included symbols remain stale.?reload=true&password=<admin>calls Lucee'sapplicationStop()(gated by the admin password as a safety mechanism). This tears down the application scope completely, forcingonApplicationStartto re-fire which re-instantiatesapplication.wo(Global.cfc) which re-evaluates the<cfinclude>chain.Suggested fixes (any of)
Make
?reload=trueactually re-includeapp/global/*.cfmby either (a) re-instantiating Global.cfc as part of the reload pipeline, or (b) tracking the included files and re-evaluating them explicitly. Either way, the behavior matches the developer's mental model: "reload means everything in the app is fresh".Document the limitation prominently in
guides.wheels.dev/v4-0-0/configuration-and-defaults/. Show side-by-side what?reload=truedoes vs.?reload=true&password=<admin>so users know which one to reach for.Print a warning during dev runs when a request notices the
app/global/*.cfmfiles have a newer mtime than the application's load time. Something like "Your app/global files have changed since startup; run?reload=true&password=<admin>for a full reload." Only in dev, only once per file-change-detection.Add a
wheels reloadCLI command that hits the password-reload URL with the password fromlucee.json. Skips the muscle-memory issue entirely.Repo / version
4.0.0-SNAPSHOT+1779vendor/wheels/events/onapplicationstart.cfc(where the reload-with-password branching lives — line ~417 is the$locationredirect post-reload, but the reload-decision logic is earlier in the file)Where found
Surfaced repeatedly during the DataPAI Phase 0 build in Titan. We added ~10 helpers across 2 helper-include files; each helper's first invocation required learning (or remembering) to use the password reload. The CLAUDE.md gotcha list captured at the end of Phase 0 (paiindustries/titan PR #3337 commit
e3ab806bb) flags this as one of 9 things to know about Wheels' dev iteration model.