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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions