Skip to content

Hot-reload contract is misdocumented: ?reload=true DOES re-fire onApplicationStart (and re-runs PackageLoader) — CLI messages and tutorial Parts 6/8 claim the opposite #3110

Description

@bpamiri

What the code actually does

The generated app's reload path (cli/lucli/templates/app/public/Application.cfc, identical in the v4.0.3 tag and the repo demo app) handles an authorized ?reload=true&password=... by calling $handleRestartAppRequest()applicationStop(). The next request starts a fresh application, so onApplicationStart re-fires in full: app/events/onapplicationstart.cfm, config/services.cfm, and $loadPackages() (vendor/wheels/events/onapplicationstart.cfc:415) all re-run.

Live proof (Lucee 7 docker, develop framework):

  1. A counter probe appended to app/events/onapplicationstart.cfm incremented on every ?reload=true&password=... (1 → 2 → 3 across three reloads).
  2. A wheels-fakepkg dropped into vendor/ on the running app was discovered and load-attempted by PackageLoader after a plain reload (its failedPackages entry updated across successive reloads as the manifest was edited) — no wheels stop && wheels start involved.

What claims the opposite

Code-emitted messages (need source edits):

  • cli/lucli/Module.cfc:846wheels reload prints: Note: onApplicationStart does NOT re-fire. For init-code edits, run \wheels stop && wheels start`.` (comment cites fresh-VM finding adding better description for hasManyCheckBox #8, 2026-04-29; live-verified still emitted on released 4.0.3 — p2-2 claim devserver-01)
  • cli/lucli/services/packages/PackagesMainCli.cfc:211-217 — comment asserts "PackageLoader runs in onApplicationStart and wheels reload doesn't re-fire that hook"; install output says Run \wheels stop && wheels start` to activate it.`

Docs (guides):

  • web/sites/guides/src/content/docs/v4-0-0/start-here/tutorial/06-authentication.mdx:560 — "Plain wheels reload does not re-run onApplicationStart"
  • 06-authentication.mdx:747 — "wheels reload does not re-fire onApplicationStart, so it doesn't re-run config/services.cfm"
  • 08-bonus-basecoat.mdx:86 — "wheels reload alone won't do — the package loader runs from onApplicationStart, which only fires on a full server boot."
  • 08-bonus-basecoat.mdx:297 — "Most common cause: you ran wheels reload instead of wheels stop && wheels start."

Root CLAUDE.md already says the correct thing ("Restart or wheels reload after install").

Likely origin of the false belief

The historical observations (fresh-VM #8, onboarding F14) most plausibly came from reloads that never actually restarted the app:

  • A missing/wrong reload password makes the Application.cfc gate silently skip the restart and serve the request normally — looks exactly like "onApplicationStart didn't re-fire". (Related: wheels reload reports "Application reloaded successfully." when the HTTP reload request fails (4xx/5xx) #3059wheels reload reports success even when the HTTP request fails.)
  • A bare dev-mode ?reload=true without password only soft-reloads app/global/*.cfm (vendor/wheels/events/EventMethods.cfc:189-202).
  • Lucee inspectTemplate=once could serve a stale compiled events file when editing init code (the CLI now purges cfclasses first — Module.cfc:835 — which removes even that case for wheels reload).

Suggested fix

  1. Decide the supported contract. The code's actual behavior (full restart on authorized reload) is the better one — recommend documenting it rather than changing code behavior.
  2. Update/remove the Module.cfc:846 note and the PackagesMainCli.cfc activation line (e.g. Run \wheels reload` (or restart) to activate it.), and fix the four tutorial passages (plus the 06:747` services.cfm variant).
  3. Keep a caveat: a reload only restarts the app when the reload password resolves — a failed password is silent (wheels reload reports "Application reloaded successfully." when the HTTP reload request fails (4xx/5xx) #3059 / Reload-password contract drift: empty password leaves ?reload=true open to anonymous restarts, warm-app wrong-password attempts are never logged or rate-limited, and the boot warning misstates behavior #3062 territory), which is worth surfacing separately.

Dedupe: distinct from #3059 (false-success reporting), #3062 (empty-password contract), and #3030 (param stripping, closed); none of PRs #3100#3108 touches this. Found by guide-behavioral-audit P2 (p2-1-starthere, claims t06-08 + t08-03).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions