Describe the bug
When a 3.x app booted under Wheels 4.0 (via wheels start) has its static assets under directory names other than the defaults — typical 3.x convention is /miscellaneous/, /javascripts/, /stylesheets/, /files/ — every CSS/JS/image request returns 404 and the site renders unstyled.
Root cause: the auto-generated rewrite.config at ~/.wheels/servers/<name>/conf/Catalina/localhost/rewrite.config only short-circuits a narrow set of directories:
RewriteCond %{REQUEST_URI} !^/(images|css|js|fonts|assets|static)/
RewriteCond %{REQUEST_URI} !\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|json|xml|txt|map)$
RewriteRule ^/(.*)$ /index.cfm/$1 [L]
Two problems:
-
Allow-list is too narrow. Real 3.x apps use directory names outside this set. A /miscellaneous/libs/bootstrap/... request gets rewritten to /index.cfm/miscellaneous/libs/bootstrap/..., dispatched into Wheels, no controller matches → 404 with the Wheels error page.
-
Negated RewriteCond chains don't reliably short-circuit on Tomcat 9 + Lucee 7.0.0.395's bundled RewriteValve. Even when the URI clearly matches the negated extension pattern (URI ends in .css), the catch-all RewriteRule still fires. Reproduced consistently — only switching to explicit [L]-flagged pass-through rules fixes it.
-
REQUEST_FILENAME -f test (the standard Apache idiom) is not usable as a workaround. It throws:
java.lang.IllegalArgumentException: The resource path [null] is not valid
at org.apache.catalina.webresources.StandardRoot.validate(StandardRoot.java:245)
at org.apache.catalina.valves.rewrite.ResolverImpl.resolveResource(ResolverImpl.java:322)
at org.apache.catalina.valves.rewrite.RewriteCond$ResourceCondition.evaluate(RewriteCond.java:85)
To Reproduce
- Start with any 3.x Wheels app whose static assets live in non-default directories — e.g.,
public/miscellaneous/, public/javascripts/, public/stylesheets/, or public/files/.
- Upgrade per the 3.x → 4.0 guide.
wheels start. Boot succeeds, redirect-to-login works, controllers render.
- Observe in the browser: every
<link rel="stylesheet"> and <script src> returns 404. Site renders unstyled.
- Access log confirms requests like
/miscellaneous/libs/bootstrap/4.3.1/css/bootstrap.min.css are rewritten to /index.cfm/miscellaneous/... and 404.
Expected behavior
The default rewrite.config should serve real files in webroot without rewriting them, regardless of which directory name the 3.x app uses.
Working fix (for the upgrade testbed)
Project-level rewrite.config using explicit [L]-flagged pass-through rules instead of negated RewriteCond chains:
RewriteRule ^/(.*)\.(cfm|cfc|cfml)$ - [L]
RewriteRule ^/(images|javascripts|stylesheets|miscellaneous|files)/.* - [L]
RewriteRule ^/.*\.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot|pdf|zip|json|xml|txt|map|webmanifest)$ - [L]
RewriteRule ^/(lucee|robots\.txt|favicon\.ico|sitemap\.xml)(/.*)?$ - [L]
RewriteRule ^/files/feedback/.* - [F]
RewriteRule ^/(.*)$ /index.cfm/$1 [L]
Suggested fixes (pick any/all)
- Broaden the default allow-list to common 3.x directory names:
images|javascripts|stylesheets|miscellaneous|files|assets|css|js|fonts|static|public.
- Switch the default rule style from negated
RewriteCond chains to explicit [L]-flagged pass-through rules, as above.
- Add an upgrade-guide note under "Static assets" calling out the
urlrewrite.xml → rewrite.config migration explicitly, with a working sample for 3.x apps. Currently this surfaces only as a boot-log hint — no upgrade-guide section mentions it.
Environment
- Wheels CLI: 4.0.0-SNAPSHOT+1779 (Homebrew tap
wheels-dev/wheels)
- Engine: Lucee 7.0.0.395 (bundled with the CLI)
- Tomcat: 9.x (bundled)
- Java: OpenJDK 21.0.11 (Homebrew)
- OS: macOS
Why this matters
This is the first wall any 3.x app upgrader hits after a successful framework boot. The boot log warns that urlrewrite.xml (Tuckey) is ignored — but does not say how the default replacement will fail. The hint that follows (💡 Create rewrite.config in your project to customize rewrite rules) understates the impact: most 3.x apps must create one or static assets won't serve.
Surfaced during a real-world 3.x→4.0 upgrade of a production app (PAI Industries' titan e-commerce storefront, ~46 models, ~5 datasources).
🤖 Filed by Claude Code while assisting with a 4.0 upgrade testbed
Describe the bug
When a 3.x app booted under Wheels 4.0 (via
wheels start) has its static assets under directory names other than the defaults — typical 3.x convention is/miscellaneous/,/javascripts/,/stylesheets/,/files/— every CSS/JS/image request returns 404 and the site renders unstyled.Root cause: the auto-generated
rewrite.configat~/.wheels/servers/<name>/conf/Catalina/localhost/rewrite.configonly short-circuits a narrow set of directories:Two problems:
Allow-list is too narrow. Real 3.x apps use directory names outside this set. A
/miscellaneous/libs/bootstrap/...request gets rewritten to/index.cfm/miscellaneous/libs/bootstrap/..., dispatched into Wheels, no controller matches → 404 with the Wheels error page.Negated
RewriteCondchains don't reliably short-circuit on Tomcat 9 + Lucee 7.0.0.395's bundled RewriteValve. Even when the URI clearly matches the negated extension pattern (URI ends in.css), the catch-allRewriteRulestill fires. Reproduced consistently — only switching to explicit[L]-flagged pass-through rules fixes it.REQUEST_FILENAME -ftest (the standard Apache idiom) is not usable as a workaround. It throws:To Reproduce
public/miscellaneous/,public/javascripts/,public/stylesheets/, orpublic/files/.wheels start. Boot succeeds, redirect-to-login works, controllers render.<link rel="stylesheet">and<script src>returns 404. Site renders unstyled./miscellaneous/libs/bootstrap/4.3.1/css/bootstrap.min.cssare rewritten to/index.cfm/miscellaneous/...and 404.Expected behavior
The default rewrite.config should serve real files in
webrootwithout rewriting them, regardless of which directory name the 3.x app uses.Working fix (for the upgrade testbed)
Project-level
rewrite.configusing explicit[L]-flagged pass-through rules instead of negatedRewriteCondchains:Suggested fixes (pick any/all)
images|javascripts|stylesheets|miscellaneous|files|assets|css|js|fonts|static|public.RewriteCondchains to explicit[L]-flagged pass-through rules, as above.urlrewrite.xml→rewrite.configmigration explicitly, with a working sample for 3.x apps. Currently this surfaces only as a boot-log hint — no upgrade-guide section mentions it.Environment
wheels-dev/wheels)Why this matters
This is the first wall any 3.x app upgrader hits after a successful framework boot. The boot log warns that
urlrewrite.xml(Tuckey) is ignored — but does not say how the default replacement will fail. The hint that follows (💡 Create rewrite.config in your project to customize rewrite rules) understates the impact: most 3.x apps must create one or static assets won't serve.Surfaced during a real-world 3.x→4.0 upgrade of a production app (PAI Industries' titan e-commerce storefront, ~46 models, ~5 datasources).
🤖 Filed by Claude Code while assisting with a 4.0 upgrade testbed