docs(web/blog): revise Upgrading from Wheels 3.x with Titan cutover lessons#2654
Conversation
…essons Post auto-published this morning (888abc2) predated the production cutover of Titan to Wheels 4.0 overnight. Revise content to reflect what surfaced during the cutover and reference the v4.0.1 fixes in flight. - Reframe count: acknowledge canonical guide's eleven breakers; this post walks the seven most likely to bite a real 3.x codebase. - Drop cfwheels-rebrand from the breaker list (that rename completed in 3.0; it is not a 4.0 breaker). Slot 2 is now redirectTo() scope tightening, which actually surfaced as TITAN-30 in titan's onrequeststart.cfm on Lucee 7. - Add a caveat to the Legacy Compatibility Adapter section: it does not shim application.wirebox; apps with WireBox bootstraps must rewrite Application.cfc before first boot regardless of the flag. - New "Things that bite at boot (from the field)" section covering the four operational gotchas titan hit at cutover: default rewrite.config asset 404s for non-standard directories, .env reloadPassword wiring through env() helper, classpath jar symlinks into Lucee Express lib/ext, and Lucee 6 -> 7 session table truncation. - Heads-up callout at the top of that section flagging that the forthcoming v4.0.1 already addresses most of these via merged framework PRs (rewrite.config defaults, wheels upgrade check coverage, guide docs); a follow-up post will point back here when v4.0.1 ships. SQLite source-of-truth (wheels-publishing-admin post id 877) is in sync with this file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: This revision adds real production signal — four operational gotchas from a live Titan cutover that the canonical breaker list does not cover — and the writing is tight. One factual accuracy concern about WireBox framing, one grep command that will silently miss the most common Application.cfc location, and one cross-engine tag-syntax nit are worth addressing before this post becomes reference material. Overall verdict: comment (no blocking issues, but three factual corrections are worth making before the post ages into the definitive upgrade resource).
Correctness
application.wirebox → application.wheelsdi is not a Wheels-native rename.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 118
What the adapter does **not** cover: it cannot shim `application.wirebox` access or the
`wirebox.system.ioc.Injector` class path. Apps that bootstrap WireBox directly in
`Application.cfc` (the canonical 2.x pattern still common in older codebases) must rewrite
that file before first boot, adapter or no adapter. The canonical breaker for this rename is
`application.wirebox` → `application.wheelsdi`.WireBox is ColdBox's DI framework — it was never a native Wheels 2.x concept. Wheels' own DI container (v4.0) is accessed via service() / injector(), not application.wheelsdi. Presenting application.wirebox → application.wheelsdi as "the canonical breaker" implies Wheels itself shipped an application.wirebox in 3.x, which is not accurate. What the paragraph is describing is a Titan-specific setup (a team that bootstrapped WireBox alongside Wheels), not a general upgrade path.
Suggested framing:
What the adapter does not cover: apps that bootstrapped a third-party DI framework — most commonly WireBox (the ColdBox standalone container) — directly in
Application.cfcalongside Wheels. The adapter cannot shimapplication.wireboxorwirebox.system.ioc.Injector. Ifgrep -rn "application.wirebox\|new wirebox.system" app/ public/finds anything, migrate to Wheels 4.0's built-in DI (service()/injector()) or keep WireBox initialized inApplication.cfcwithout expecting the adapter to bridge the gap.
The application.wheelsdi reference should be dropped or sourced — it does not appear in CLAUDE.md or the framework docs.
Detection grep for breaker #2 misses the most common Application.cfc path.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 46 (table) and line 65 (body)
`grep -rn "redirectTo(" app/events/ public/Application.cfc`public/Application.cfc is Titan's project layout, not the standard one. Most Wheels apps put Application.cfc at the project root. Readers following this grep verbatim will silently miss any redirectTo() call in their Application.cfc if it lives at the root or in app/.
Suggested:
grep -rn "redirectTo(" app/events/ Application.cfc
Or to be safe across layouts:
grep -rn "redirectTo(" app/events/ $(find . -maxdepth 2 -name "Application.cfc")
Cross-engine
location url="..." addToken=false; — dropped cf prefix in tag-in-script example.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 72
// after
location url="/account/logOut" addToken=false;In Lucee cfscript, the tag-in-script shorthand for <cflocation> is cflocation url="..." addToken=false; (with the cf prefix). The location form without the prefix is accepted by some Lucee versions but is not documented as the canonical shorthand and will not work in Adobe CF. The breaker itself is Lucee 7–specific, but readers may be running on other engines or mixed environments.
The cross-engine–safe cfscript form is:
// after (works on Lucee + Adobe CF)
cflocation(url="/account/logOut", addToken=false);Docs
Minor note: the excerpt in the frontmatter (line 12–16) still reads "seven breaking changes." After this revision the post's own intro distinguishes the seven featured breaks from the canonical guide's eleven, so the excerpt is still accurate — no change needed. Just confirming.
The updatedAt bump to 2026-05-13T14:56:19.000Z is correct and matches the PR body.
Commits
Single commit docs(web/blog): revise Upgrading from Wheels 3.x with Titan cutover lessons — valid type, scope, and subject. Conforms to commitlint.config.js.
Wheels Bot — Reviewer B (round 1)A's review contains one significant false positive on its most prominent finding, and two valid findings the author should act on. I cannot align on a verdict while a false positive of this weight is in play. SycophancyNone detected. A did not approve despite finding issues, avoided 'looks great' language, and called the verdict 'comment' rather than inflating it. False positivesFinding 1 — WireBox/wheelsdi framing. A claims that This is wrong. Missed issuesNone detected. A scanned the new operational section, checked the frontmatter timestamp, and confirmed the commit message. The 'Things that bite at boot' section contains field-reported operational details; nothing in it is obviously contradicted by the framework source. Verdict alignmentA's verdict ('comment') is consistent with the two valid findings (grep path, ConvergenceNot aligned. The false positive on finding 1 must be resolved before we proceed. If A retracts that finding and the remaining two (grep path, |
The /v4-0-0-snapshot/ path on guides.wheels.dev currently 404s; the canonical guide is served at /v4-0-0/. Replace nine references across the body (upgrade guide, middleware pipeline, route model binding, query builder, background jobs, packages). SQLite source (post id 877) is in sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer A response (round 1)
B is correct on the false positive. I checked vendor/wheels/Injector.cfc:45 — the Injector self-registers at application.wheelsdi = this;, and vendor/wheels/tests/specs/cli/ApplicationCfcInjectorAssignmentSpec.cfc explicitly asserts that generated Application.cfc templates assign application.wheelsdi = new wheels.Injector(...) and route all getInstance calls through it. application.wheelsdi is a real, load-bearing Wheels 4.0 concept and the blog post's migration framing is accurate. I retract Finding 1 in its entirety, including the suggested rewrite. The two remaining findings (grep path silently missing the root-level Application.cfc, and the non-portable location tag-in-script shorthand) are still actionable and were not challenged by B.
Conceded points
- Finding 1 (WireBox/wheelsdi framing): Retracted.
application.wheelsdiis confirmed atvendor/wheels/Injector.cfc:45and tested invendor/wheels/tests/specs/cli/ApplicationCfcInjectorAssignmentSpec.cfc. The blog's sentence naming it as the canonical migration target forapplication.wireboxis accurate. My suggested rewrite would have removed the only concrete endpoint readers need to migrate toward. B was right to call this a false positive.
Defended points
-
Finding 2 (grep path):
grep -rn "redirectTo(" app/events/ public/Application.cfchardcodes a Titan-specific layout. Most Wheels apps placeApplication.cfcat the project root; readers following this verbatim will silently miss calls there. The fix (grep -rn "redirectTo(" app/events/ Application.cfcor afind-based variant) is still valid and was not disputed. -
Finding 3 (
cflocationportability):location url="..." addToken=false;without thecfprefix is not portable to Adobe CF and is not documented as the canonical shorthand even on Lucee. The cross-engine safe form iscflocation(url="...", addToken=false);. The post's own context is Lucee 7-specific, but since the post is framed as a general upgrade guide this nit is still worth noting. Not disputed by B.
Updated verdict
Comment — same label as the initial review, now resting on two actionable findings rather than three. Both (grep path and cflocation syntax) are worth a one-line author fix before the post becomes the definitive upgrade reference, but neither blocks merge.
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: The new commit on this SHA (3fe402558) is a correct, focused link fix — swapping five stale /v4-0-0-snapshot/ paths to /v4-0-0/ across the deprecations and "where to go next" sections. No new issues introduced. Two findings from the previous review cycle remain unaddressed in the current file and are worth a one-line fix before this post becomes the definitive upgrade reference. Verdict: comment.
Correctness
Detection grep hardcodes a Titan-specific path and silently misses the standard layout.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 46 (table) and line 65 (body)
grep -rn "redirectTo(" app/events/ public/Application.cfc
public/Application.cfc is the path Titan used; most Wheels apps place Application.cfc at the project root. A reader following this command verbatim will silently skip any redirectTo() call in their root-level file, miss the breaker entirely, and hit the runtime error in production.
Suggested fix (works for both layouts):
grep -rn "redirectTo(" app/events/ Application.cfc
Or defensively, to catch any depth:
grep -rn "redirectTo(" app/events/ $(find . -maxdepth 2 -name 'Application.cfc')
Cross-engine
location tag-in-script shorthand without cf prefix is not portable to Adobe CF.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 72
// after
location url="/account/logOut" addToken=false;location url="..." addToken=false; (without the cf prefix) is accepted by some Lucee versions but is not the documented canonical form and fails to parse on Adobe CF. The post's scenario is Lucee 7-specific, but the blog is framed as a general upgrade guide and readers on other engines may copy this snippet.
The cross-engine-safe cfscript form is:
// after (works on Lucee + Adobe CF)
cflocation(url="/account/logOut", addToken=false);Commits
Both commits conform to commitlint.config.js:
docs(web/blog): revise Upgrading from Wheels 3.x with Titan cutover lessons— valid type, scope, subjectdocs(web/blog): fix 404 links — v4-0-0-snapshot → v4-0-0 in upgrade post— valid type, scope, subject
The link fix in 3fe402558 is correct — all five /v4-0-0-snapshot/ → /v4-0-0/ replacements in the deprecations and "where to go next" sections are accurate.
Neither finding blocks merge. Both are one-line author fixes before the post hardens into reference material.
|
Reviewer B round 1 test |
Wheels Bot - Reviewer B (round 1)A's review on this SHA is clean. The two remaining findings from the previous cycle are real, the link-fix commit assessment is accurate, and the verdict is consistent with the findings. Aligning on converged-changes. SycophancyNone detected. A carried two unresolved findings forward from the previous cycle rather than approving on the strength of the link fix, and used comment rather than inflating to approve. False positivesNone detected. Grep path finding: The diff confirms the grep command targets app/events/ and public/Application.cfc at both the table row and the body paragraph. public/Application.cfc is not a standard Wheels layout - Application.cfc lives at the project root in a conventional Wheels app; public/ is the static-asset directory. A reader following the command verbatim silently skips any redirectTo() call at the root level. A's finding is accurate. cflocation portability finding: The diff confirms the code block uses the location url=... form without the cf prefix. Tag-in-script without the cf prefix is not portable to Adobe CF. The canonical cross-engine safe form is cflocation(url=..., addToken=false). A's finding is accurate. Link-fix assessment: A characterizes the new commit as swapping five stale v4-0-0-snapshot paths. The diff shows 9 link instances across 8 changed lines (6 unique paths). The undercount is immaterial - A correctly validated that the fix is accurate and no regressions were introduced. Missed issuesNone detected. The PR is a documentation-only change (blog post). No CFC code, no security surface, no cross-engine CFML concerns. Both commits conform to commitlint.config.js. Verdict alignmentA's comment verdict is consistent with two concrete, actionable, non-blocking findings. Appropriate. ConvergenceAligned. Both remaining findings are verified and actionable: (1) change public/Application.cfc to Application.cfc in the grep command, and (2) change the location tag-in-script shorthand to the portable cflocation(...) function-call form. Neither blocks merge, but both improve the accuracy of a post that will serve as a reference for 3.x upgraders. Joint recommendation: apply both one-line fixes. |
Wheels Bot — Propose FixNo triage or research comment was found on this item, and If a human wants the bot to take action on this PR, please re-target a real issue, or open a fresh issue describing the desired change and let the triage stage classify it first. |
Wheels Bot -- Reviewer B (round 1)A correctly retracted the wheelsdi false positive and has now defended two findings I did not dispute in round 1. Both check out against the diff. I am aligned on a converged-changes outcome. SycophancyNone detected. A explicitly verified the source file before retracting, and maintained the two remaining findings under challenge rather than caving. False positivesNone in the response. Both defended findings are substantiated:
Missed issuesNone detected. Commit messages ( Verdict alignmentThe ConvergenceAligned on changes needed. Two concrete fixes:
Neither is a blocker, but both are worth landing before this post becomes the canonical 3.x to 4.0 field guide. |
- Drop the non-standard `public/` prefix from the `redirectTo()` grep command in the detection table and body so readers on a standard Wheels install actually find matches in their root `Application.cfc`. - Replace the Lucee-only tag-in-script `location url=...` snippet with the cross-engine portable `cflocation(url=..., addToken=false);` function-call form in the "after" code block, and adjust the framing text accordingly. Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Wheels Bot — Address Review (round 1)Applied consensus findings from Reviewer A and Reviewer B's convergence (round 1):
Skipped (not in consensus):
This is a docs-only PR ( The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: The latest commit (0f469546d) correctly addresses both findings carried forward from the previous review cycle: the grep command in the detection table and body text now uses Application.cfc (root-level, standard layout) instead of public/Application.cfc (Titan-specific path), and the non-portable location url=...; tag-in-script snippet is replaced with the cross-engine-safe cflocation(url=..., addToken=false); function-call form. One new minor accuracy nit and one metadata nit are worth a one-line author fix before the post ages into reference material. Verdict: comment.
Correctness
java.lang.ClassException is not a real Java exception class.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 147
The text reads: throws java.lang.ClassException: cannot load class ...
java.lang.ClassException does not exist in the standard Java API or in the OSGi/Lucee runtime. The actual exceptions thrown when the JVM cannot resolve a class from a JAR are:
java.lang.ClassNotFoundException— thrown byClass.forName()/ClassLoader.loadClass()when the class name is not found on the classpathjava.lang.NoClassDefFoundError— thrown at runtime when the class was present at compile time but cannot be found when the JVM links the call site
A reader whose error tracker shows ClassNotFoundException or NoClassDefFoundError and searches for ClassException will find nothing and may doubt whether this is the same failure. The Lucee OSGi error log typically surfaces these as ClassNotFoundException: <fqcn> or a bundle-activation error wrapping NoClassDefFoundError.
Suggested fix on line 147: replace java.lang.ClassException: cannot load class with a reference to ClassNotFoundException (from Class.forName / driver registration) or NoClassDefFoundError (if the JAR loaded but the class is linked at call time).
Docs
updatedAt was not bumped in the latest commit.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 5
The stored value is updatedAt: 2026-05-13T15:07:50.000Z but the latest commit was authored at 2026-05-13T15:17:38Z — ten minutes later. If the blog system surfaces this timestamp to readers or in the Atom/RSS feed, the post will appear not to have changed since the link-fix commit. Minor, but worth a one-line bump before merge.
Commits
All three commits conform to commitlint.config.js:
docs(web/blog): address Reviewer A/B consensus findings (round 1)— valid type, scope, subject; addresses both prior findings accurately.docs(web/blog): fix 404 links — v4-0-0-snapshot -> v4-0-0 in upgrade post— valid.docs(web/blog): revise Upgrading from Wheels 3.x with Titan cutover lessons— valid.
Neither finding blocks merge. The ClassException name is the only factual inaccuracy in an otherwise solid field-tested guide.
The previous framing referenced "an AppSerial mismatch in onrequeststart.cfm redirecting to /account/logOut" — that was a Titan-specific example that leaked an internal detail. Rewrite for the canonical Wheels developer audience: frame the scenario as detecting a re-auth condition (stale session, server-side state change, maintenance flag) and interrupting the request to send the user to the login form. Genericize the code example to controller="sessions"/action="new" → /login. SQLite source (post id 877) is in sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wheels Bot — Reviewer B (round 1)A's two findings are both genuine accuracy issues — no false positives. However, A missed one grep-path defect in the new content that is structurally identical to the primary finding from the previous review cycle. Not aligned this round. SycophancyNone detected. A did not approve without evidence, carried forward concrete findings, and used False positivesNone detected.
Missed issuesLine 118 — WireBox grep searches the wrong directories. The adapter-section text reads: "Apps that bootstrap WireBox directly in The canonical location for WireBox initialisation is The fix is the same structural pattern as the previous consensus: replace Verdict alignmentA's ConvergenceNot aligned. The WireBox grep path at line 118 is a concrete missed issue that A needs to acknowledge. If A concedes it, the three combined findings (ClassException, updatedAt, WireBox grep) are all actionable one-liners and a |
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: The latest commit (a20e257a3) delivers exactly what its message claims — the redirectTo() example is now framed for the canonical Wheels developer audience rather than leaking Titan-internal details (AppSerial mismatch, /account/logOut). The genericized scenario (stale session, re-auth condition, maintenance flag) and the updated cflocation() code example are accurate and portable. One unresolved finding from the previous review cycle (java.lang.ClassException) was not addressed in this commit; one new minor nit (missing path prefix on onapplicationstart.cfm) surfaced in the changed section. Verdict: comment — no blocking issues, but the ClassException name is a factual inaccuracy that will mislead readers searching error logs.
Correctness
java.lang.ClassException remains unfixed from the previous review cycle.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 147
throws `java.lang.ClassException: cannot load class …`
This was flagged in the 0f469546d review round and was not addressed in the current commit. java.lang.ClassException does not exist in the Java standard library. The two real exceptions a reader will see in this scenario are:
java.lang.ClassNotFoundException— thrown byClassLoader.loadClass()/Class.forName()when the class cannot be found on the classpathjava.lang.NoClassDefFoundError— thrown at link time when the class was on the classpath at build time but is absent at runtime
A reader whose error tracker shows ClassNotFoundException and searches for ClassException will find nothing and may doubt whether this is the same failure. The fix is a one-word change: replace ClassException with ClassNotFoundException (the more common case for missing JARs during driver registration).
Docs
onapplicationstart.cfm missing its app/events/ path prefix on line 61.
File: web/content/blog/posts/upgrading-from-wheels-3x.md, line 61
Calls from request-lifecycle event handlers (`app/events/onrequeststart.cfm`, `onapplicationstart.cfm`) throw …
onrequeststart.cfm is correctly qualified with its directory (app/events/), but onapplicationstart.cfm is listed without any path. Both files live in app/events/ (confirmed at app/events/onapplicationstart.cfm). A reader skimming the text will have to guess where the second file lives. Suggest: app/events/onrequeststart.cfm, app/events/onapplicationstart.cfm.
Commits
All four commits in this PR conform to commitlint.config.js:
docs(web/blog): rewrite redirectTo() example in developer voice— valid type, scope, subject; change is well-scoped and accurate.docs(web/blog): address Reviewer A/B consensus findings (round 1)— valid.docs(web/blog): fix 404 links — v4-0-0-snapshot → v4-0-0 in upgrade post— valid.docs(web/blog): revise Upgrading from Wheels 3.x with Titan cutover lessons— valid.
Neither finding blocks merge. The ClassException name is the only factual inaccuracy remaining in an otherwise solid, field-tested guide.
Wheels Bot — Reviewer B (round 1)A's two findings are accurate and actionable. However, A missed one grep-path defect that is structurally identical to the primary finding from the previous cycle — the WireBox detection command silently skips the canonical location of the code it's trying to find. Not aligned this round. SycophancyNone detected. A used False positivesNone detected.
Missed issuesWireBox grep targets wrong directories (diff line 72). The adapter-limits paragraph ends with:
The fix follows the same pattern as the previous consensus: replace Verdict alignmentA's ConvergenceNot aligned. The WireBox grep path at diff line 72 is a concrete missed issue A needs to address. If A concedes it, the three combined findings (ClassException, onapplicationstart.cfm path, WireBox grep) are all one-liners and a |
There was a problem hiding this comment.
Wheels Bot — Reviewer A response (round 1)
B raised one missed issue (WireBox grep path) and confirmed both of A's existing findings as genuine. I concede the missed issue: B is correct that grep -rn "application.wirebox\|new wirebox.system" app/ public/ silently misses Application.cfc at the project root — the canonical location for WireBox bootstrap in a standard Wheels app. This is structurally identical to the redirectTo() grep defect fixed in the previous cycle. Both of my existing findings (java.lang.ClassException, onapplicationstart.cfm path prefix) were confirmed by B and stand. Updated verdict: comment, with all three findings now confirmed. Converged-changes outcome follows if B aligns.
Conceded points
- B's missed issue (WireBox grep path, diff line 118): Verified in the diff. The adapter-limits paragraph reads:
If grep -rn "application.wirebox\|new wirebox.system" app/ public/ finds anything, plan the bootstrap rewrite up front.The canonical location for WireBox bootstrap code in a Wheels app isApplication.cfcat the project root — not underapp/orpublic/. A reader on a standard install runs this command verbatim and gets zero matches even when their rootApplication.cfcbootstraps WireBox. The fix follows the same pattern as the prior consensus:grep -rn "application.wirebox\|new wirebox.system" app/ Application.cfc.
Defended points
-
Finding 1 (
java.lang.ClassException, line 147): B confirmed this finding as accurate.java.lang.ClassExceptiondoes not exist in the Java standard library or standard OSGi runtime. The actual exceptions a reader will see in error logs arejava.lang.ClassNotFoundException(thrown byClassLoader.loadClass()/Class.forName()) andjava.lang.NoClassDefFoundError(thrown at link time when the class is absent at runtime). A reader whose error tracker showsClassNotFoundExceptionand searches forClassExceptionfinds no match and may doubt whether this is the right section. Fix is a one-word change. -
Finding 2 (
onapplicationstart.cfmmissing path prefix, line 61): B confirmed this finding as accurate. The diff line readsapp/events/onrequeststart.cfm,onapplicationstart.cfm— the second file is listed without itsapp/events/directory prefix. Both live inapp/events/per the CLAUDE.md directory layout. Fix: qualify both consistently asapp/events/onrequeststart.cfm,app/events/onapplicationstart.cfm.
Updated verdict
Comment — same as initial review, now with three confirmed actionable findings: (1) correct java.lang.ClassException to ClassNotFoundException (or NoClassDefFoundError) at line 147, (2) add app/events/ prefix to the bare onapplicationstart.cfm reference at line 61, (3) change the WireBox grep from app/ public/ to app/ Application.cfc at line 118. All three are one-liners; none block merge. A converged-changes outcome follows.
Summary
The post auto-published this morning (888abc2) was drafted before the production cutover of Titan to Wheels 4.0. This revision folds in what we learned overnight and points forward at the v4.0.1 fixes already merged on develop.
Key changes
redirectTo()scope tightening — Wheels 4.0 / Lucee 7 makesredirectTo()strictly controller-scoped, so calls fromapp/events/onrequeststart.cfmand similar request-lifecycle handlers throwNo matching function [REDIRECTTO] found. This is the regression that surfaced as TITAN-30 in production.application.wireboxor thewirebox.system.ioc.Injectorclass path. Apps that bootstrap WireBox directly inApplication.cfc(the canonical 2.x pattern) must rewrite that file before first boot, adapter or no adapter.rewrite.config404s static assets in non-standard directories (/miscellaneous/,/javascripts/,/stylesheets/,/files/)reloadPassword=...in.envdoes not satisfy the framework empty-check; must wire viaset(reloadPassword=env("WHEELS_RELOAD_PASSWORD"))lib/ext/if you came from CommandBoxlibDirsInvalidClassExceptionbeforeonrequeststart.cfmruns; AppSerial kill-switch can't fire, so the cookies are stuck. One-time fix: truncate the session table after cutover.rewrite.configdefaults via fix(cli): provision project rewrite.config on wheels start for 3.x upgrades #2643, expandedwheels upgrade checkvia fix(cli): scan seven more 3.x → 4.x breakers in wheels upgrade check #2645, canonical-guide docs via docs(web/guides): clarify legacy adapter does not shim application.wirebox rename #2636/docs(web/guides): document reloadPassword must be set via env() in config/settings.cfm #2637/docs(web/guides): document allowCorsRequests global setting still works in 4.0 #2638/docs(releases): reconcile breaking-change count in 4.0 upgrade docs #2644). A follow-up post when v4.0.1 ships will point back here.Source of truth
The publishing-admin's SQLite (post id 877) has been resynced to match this file. Body grew 14,371 → 19,487 bytes.
updatedAtbumped to 2026-05-13T14:56:19Z.Test plan
cflocationandset(reloadPassword=...)render with syntax highlightingupdatedAtshows on the post page (or in feeds)guides.wheels.dev/v4-0-0-snapshot/upgrading/3x-to-4x//blog/upgrading-from-wheels-3xpost-merge🤖 Generated with Claude Code