Skip to content

onError unconditionally re-creates the DI container, silently wiping all user service registrations after any error page #3061

Description

@bpamiri

Summary

public/Application.cfc::onError() begins with:

application.wheelsdi = new wheels.Injector("wheels.Bindings");

unconditionally (line 345). Every uncaught exception — including routine development-mode 404 error pages (Wheels.RouteNotFound, Wheels.ViewNotFound, Wheels.RecordNotFound) — replaces the live container, discarding every registration made in config/services.cfm plus all cached singletons and request-scoped flags. From that moment, inject()-declared services stop resolving: $resolveInjectedServices() silently skips (containsInstance is false) and controllers crash with Element <SERVICE> is undefined in THIS. until the next reload. The request-lifecycle guide's guarantee that beforeAction filters "can rely on injected services" is broken by the first stray 404.

Repro (adobe2023 harness, develop @ f668c50 — code path is engine-neutral)

A/B in one warm app:

# 1. services.cfm registers probeService; Probe controller does inject("probeService")
#    and a private before-filter renders this.probeService.greet()
curl -s 'http://localhost:61753/probe/svc'      # -> 200 SVC-OK  (containsInstance("probeService") = YES)

# 2. one ordinary dev 404 error page
curl -s -o /dev/null 'http://localhost:61753/zzz/a/b/c'   # -> Wheels.RouteNotFound dev error page

# 3. same request as step 1
curl -s 'http://localhost:61753/probe/svc'      # -> 500 "Element PROBESERVICE is undefined in THIS."

Diagnostics confirmed application.wheelsdi still exists and the controller's declared injectedServices() still lists probeService — only the container contents were wiped.

Root cause

The re-creation is a fallback for "the Wheels global never came up" (per the surrounding comment), but it runs even when the app is healthy. vendor/wheels/Bindings.cfc:53-56 itself warns that constructing a fresh instance "would clobber application.wheelsdi". Shipped in the 4.0.x line — the line dates to #2623 (2026-05-12), so it is in 4.0.3 GA. Present in public/Application.cfc:345, cli/lucli/templates/app/public/Application.cfc:339, and both examples/*/public/Application.cfc:313.

Proposed direction

Guard the fallback: only construct a new Injector when !StructKeyExists(application, "wheelsdi") (and likewise only rebuild application.wo when missing). Apply to all four same-lineage copies (repo demo app, CLI template, both examples).

Acceptance

  • After a dev-mode 404/error page, previously registered services still resolve (service(), inject(), containsInstance() all intact) — spec or harness check.
  • onError still works when the app genuinely failed to start (the fallback path keeps rendering the error page).
  • Template + examples updated in the same PR.

Reported by the 2026-06-11 guide behavioral audit (P1 batch 1): request-lifecycle verifier (claim before-filters-after-config-di, code-broken — root cause 1 of 2; root cause 2 is the var di = injector(); Adobe boot crash, filed separately). Findings catalog: docs/superpowers/audits/2026-06-guide-audit-findings.md.

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