Skip to content

fix(config): rename local.url in $buildRedirectUrl so url scope resolves on Adobe CF - #3056

Closed
wheels-bot[bot] wants to merge 2 commits into
developfrom
fix/bot-3053-adobe-cf-every-password-reload-and-url-env-switch
Closed

fix(config): rename local.url in $buildRedirectUrl so url scope resolves on Adobe CF#3056
wheels-bot[bot] wants to merge 2 commits into
developfrom
fix/bot-3053-adobe-cf-every-password-reload-and-url-env-switch

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Renames the string local local.url to local.redirectUrl inside $buildRedirectUrl() so the unscoped url.* reads added by #3036 resolve to the URL scope on every engine. #3036 layered six StructKeyExists(url, "reload") / url.password reads into a function that has always assigned a string local named url (the redirect target, built from cgi.path_info / cgi.script_name). On Adobe ColdFusion unscoped name resolution binds the bare url reads to that string local before the URL scope and throws ScopeCastException ("attempted to dereference a scalar variable of type ... java.lang.String as a structure") — an HTTP 500 fired before applicationStop(), so every password-gated reload (?reload=true&password=...), every URL environment switch (?reload=<env>&password=...), the #3038 allowEnvironmentSwitchViaUrl=true path, and wheels reload (drives the same URL) became non-functional on Adobe. Lucee/BoxLang masked the bug because url is a reserved scope that always wins. This is CLAUDE.md cross-engine anti-pattern #11 verbatim.

The rename is applied to all four same-lineage copies of public/Application.cfc (demo app, wheels new app template, and the tweet + starter-app examples), matching the parity contract enforced by ReloadEnvironmentSwitchParitySpec.cfc. Behavior is otherwise identical on every engine — it is a pure local-variable rename.

Refs #3053

Related Issue

Refs #3053

(Using Refs, not Fixes: the root-cause rename is complete and a structural regression guard is added, but the issue's runtime acceptance criteria — ?reload=...&password=... returning 302-and-restart on adobe2023/2025, and the optional extension of the #3044 non-dev smoke probes to exercise the Adobe password-reload path — require the Adobe docker harness, which the bot's Lucee-only local runner cannot exercise. Those are left for CI's compat-matrix and human review to confirm before the issue is closed.)

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-off — commit carries Signed-off-by:
  • Tests — new vendor/wheels/tests/specs/cli/BuildRedirectUrlScopeShadowSpec.cfc (failing → passing)
  • Framework Docs — left unchecked (handled separately by bot-update-docs.yml)
  • AI Reference Docs — left unchecked (handled separately by bot-update-docs.yml)
  • CLAUDE.md — left unchecked (handled separately by bot-update-docs.yml); note the fix is already covered by existing anti-pattern New master #11
  • Changelog fragmentchangelog.d/3053-buildredirecturl-url-scope-shadow.fixed.md
  • Test runner passes — see Test Plan

Test Plan

TDD: failing → passing structural spec. The runtime repro requires the Adobe docker harness (the bug is invisible on Lucee, which the local SQLite runner uses), so the regression guard asserts the static defect is gone: no reserved-scope local.url may coexist with unscoped url.* reads inside $buildRedirectUrl() in any of the four copies. This is the "a reserved-scope shadowing in this file can't ship green again" coverage the issue asks for.

Note on the runner: the bot's environment ran the cli layer via the same HTTP test endpoint tools/test-local.sh uses internally, against the workflow's already-running Lucee 7 server (the script's sed -i '' placeholder step is BSD-only and aborts on the GNU/Linux CI host). The result is equivalent to bash tools/test-local.sh wheels.tests.specs.cli.

For human/CI verification (Adobe runtime ACs, unreachable from the Lucee-only local runner):

  • adobe2023 + adobe2025: ?reload=true&password=<pw> → 302 and the app actually restarts.
  • adobe2023/2025: ?reload=testing&password=<pw> from development switches environment (302 preserves reload+password).
  • adobe2023/2025: explicit set(allowEnvironmentSwitchViaUrl=true) in testing permits ?reload=development&password=<pw>; unset stays blocked.

…ves on Adobe CF

Issue #3053: #3036 added unscoped url.* reads to $buildRedirectUrl() in
public/Application.cfc, a function that already declared a string local
named `url` (the redirect target). On Adobe CF unscoped name resolution
binds the bare `url` reads to that string local before the URL scope and
throws a ScopeCastException (HTTP 500) before applicationStop(), so every
password-gated reload and URL environment switch became non-functional on
Adobe; Lucee masked it because `url` is a reserved scope that always wins.

Rename the local to local.redirectUrl across all four same-lineage copies
of public/Application.cfc (demo app, app template, tweet + starter-app
examples) so the url.* reads resolve to the URL scope on every engine.
This is CLAUDE.md cross-engine anti-pattern #11 verbatim.

Adds a structural regression guard (BuildRedirectUrlScopeShadowSpec) that
fails if a reserved-scope local.url reappears alongside unscoped url reads
in any copy.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Add a second example to CLAUDE.md anti-pattern #11 showing that a
`local.*` variable named after a reserved scope (e.g. `local.url`) can
shadow bare scope reads in the same function — the inverse of the named-
parameter case. On Adobe CF the string local wins over the URL scope,
causing `ScopeCastException`; on Lucee the reserved scope wins. Refs #3053.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR fixes #3053 by renaming the string local local.url to local.redirectUrl inside $buildRedirectUrl() across all four same-lineage copies of public/Application.cfc, so the unscoped url.* reads added in #3036 resolve to the URL scope instead of a string local on Adobe CF (CLAUDE.md cross-engine anti-pattern #11, same family as #2591). I verified the rename is complete — grep finds zero remaining local.url references in any of the four copies — and the new structural spec mirrors the established ReloadEnvironmentSwitchParitySpec.cfc pattern (same repo-root resolution, same IIFE loop capture, same four-target parity list), so it carries no novel cross-engine compile risk for the bundle. The commit is a valid conventional commit with a matching DCO sign-off, the changelog fragment is in place, and the PR targets develop. Verdict: comment — the change itself is clean; the findings below are minor test-robustness nits plus one merge-gate reminder.

Tests

  1. The spec's vacuity guard is satisfied by the PR's own explanatory commentvendor/wheels/tests/specs/cli/BuildRedirectUrlScopeShadowSpec.cfc:84-90. The sanity assertion

    expect(
        fnBody contains 'StructKeyExists(url, "reload")'
    ).toBeTrue(...)

    is meant to fail if the #3036 URL-scope reads ever move out of $buildRedirectUrl() (per the comment at spec lines 81–83: "If these reads ever move out the test below would pass vacuously"). But the NOTE comment this PR adds inside the function body contains the exact same literal — e.g. public/Application.cfc:472: // (StructKeyExists(url, "reload"), ...) bind to a string local named — and the comment sits inside the slice bodyOf() extracts. So if the real reads are later removed while the comment stays, the guard still passes and the spec's "must still read the URL scope" premise silently stops being checked. This is the fragility CLAUDE.md anti-pattern #14 ("Strip CFML Comments Before Source-Scanning", from #2595) warns about, applied to a test. Two cheap fixes, either is fine: (a) strip // line comments from fnBody before both assertions (the same hardening would also prevent a future false failure of the local\.url\b check if someone documents the rename inline as literal local.url), or (b) drop the StructKeyExists(url, "reload") literal from the four in-function comments so only real code can satisfy the guard. The main assertion (zero local.url in the body) is unaffected and works today — this only weakens the meta-guard, hence nit severity.

  2. Adobe runtime acceptance criteria remain unverified — treat compat-matrix as the merge gate. The PR body discloses this honestly (Lucee-only local runner; Refs not Fixes), and the structural spec is the right TDD artifact for what the bot environment can reach. But since the entire point of the fix is Adobe CF behavior, a maintainer should confirm adobe2023/adobe2025 are green in compat-matrix — ideally with the manual ?reload=true&password=... → 302 probe from the PR's "For human/CI verification" list — before flipping this out of draft and closing #3053. CLAUDE.md's "Verify Adobe CF fixes locally before pushing" guidance applies to whoever picks this up with Docker access.

Verified clean (for the record)

  • Correctness / cross-engine: all six local.url references per copy are renamed (matches the spec's before-fix Expected [0] Actual [6]); call sites in $handleRestartAppRequest already used local.redirectUrl (public/Application.cfc:426, :466) so naming is now consistent. The spec's slice boundaries hold in all four files (loadEnvFile( immediately follows $buildRedirectUrl( in each). The spec's idioms (arrow functions, IIFE loop capture, contains operator, raw-string regex) all have prior art in the same directory (ReloadEnvironmentSwitchParitySpec.cfc:55-66), and none of the compile-crash anti-patterns (#5 inline-closure constructor arg, unescaped # in strings) are present.
  • Commits: single commit, fix(config): … header is 86 chars, type/subject valid per commitlint.config.js, sign-off matches the author identity.
  • Docs: changelog fragment changelog.d/3053-buildredirecturl-url-scope-shadow.fixed.md follows the fragment convention (no direct CHANGELOG.md edit); the underlying gotcha is already documented as CLAUDE.md anti-pattern #11, so no new reference-doc obligation.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer

TL;DR: This PR fixes #3053 — the local.url string local in $buildRedirectUrl() shadowed the reserved url scope on Adobe CF, turning every password-gated reload and URL environment switch into an HTTP 500 — by renaming the local to local.redirectUrl across all four same-lineage copies of public/Application.cfc, with a structural regression spec and a CLAUDE.md anti-pattern #11 expansion. The fix is correct, complete, and exactly matches the documented anti-pattern; I verified at the head SHA that no local.url (or var url / arguments.url) remains in any of the four files, the caller already consumes the return value, and both commits are commitlint-clean with matching DCO sign-offs. Verdict: comment — no blocking findings, two non-blocking test-robustness notes and one trivial metadata nit below.

Tests

  1. The vacuous-pass sanity check can itself pass vacuously via the new NOTE comment. vendor/wheels/tests/specs/cli/BuildRedirectUrlScopeShadowSpec.cfc (~line 88) guards against the main assertion going vacuous:

    expect(
        fnBody contains 'StructKeyExists(url, "reload")'
    ).toBeTrue(

    But the NOTE comment this PR adds inside $buildRedirectUrl() contains that exact literal — public/Application.cfc:472 reads // (StructKeyExists(url, "reload"), ...) bind to a string local named — so if a future refactor moved the #3036 reads out of the function, the sanity check would still pass on the comment text alone. More broadly, the spec does substring/regex matching over CFML source without stripping comments first, which is CLAUDE.md anti-pattern #14 ("Strip CFML Comments Before Source-Scanning") — and that cuts both ways: a future comment mentioning local.url inside the function body would false-fail the reMatchNoCase("local\.url\b", fnBody) assertion (the PR's own NOTE comments carefully avoid that literal, which shows the hazard is real). Suggested fix: run fnBody through a comment-stripper before both checks (prior-art helpers are listed under anti-pattern #14, e.g. cli/lucli/services/Analysis.cfc::$stripCfmlComments(); a local strip of //-lines and /* */ blocks inside bodyOf would also do). Non-blocking — today the real reads exist (public/Application.cfc:504) and the four files contain no local.url anywhere, so both assertions currently exercise real code.

  2. Adobe runtime acceptance criteria remain unverified — disclosed honestly in the PR body (the bot's runner is Lucee-only; the bug is invisible on Lucee). The rename's Adobe behavior follows directly from anti-pattern #11 semantics and the structural spec pins the static defect, so this is fine to land on the strength of compat-matrix CI — but the issue's runtime ACs (?reload=true&password=... → 302-and-restart on adobe2023/2025) should be confirmed by the compat-matrix run or a human with the Adobe harness before #3053 is closed. The PR's use of Refs rather than Fixes is the right call.

Otherwise test coverage is solid: BDD spec extending wheels.WheelsTest, failing-→-passing TDD evidence in the PR body, and the spec mirrors ReloadEnvironmentSwitchParitySpec.cfc prior art faithfully (same expandPath("/wheels/../..") root resolution, same IIFE loop-variable capture, same four-copy parity contract).

Docs

  1. PR-body checklist inconsistency (trivial): the Feature Completeness Checklist leaves "CLAUDE.md" unchecked with "handled separately by bot-update-docs.yml", but commit e5c8b0f4c in this PR does update CLAUDE.md (the anti-pattern #11 expansion — a good addition, accurately describing the Adobe-resolves-local-first vs Lucee-scope-wins inversion). Worth ticking the box so the record matches the diff.

Changelog fragment is correctly done: changelog.d/3053-buildredirecturl-url-scope-shadow.fixed.md, valid .fixed type, complete bullet with issue ref, no direct CHANGELOG.md edit.

Commits

Both commits conform: fix(config): … (86-char header) and docs: … (71-char header), valid types, bodies explain the why, and Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> matches the commit author on both. The PR title is itself a valid conventional-commit header for the squash-merge gate.

Verified clean

  • Correctness: rename is internally consistent in all four copies (assignment branches, query-string re-append at public/Application.cfc:538, and the return); git grep 'local\.url\b' over the four files at the head SHA returns nothing; no other reserved-scope shadows (var url, arguments.url) in those files.
  • Cross-engine: the change removes an engine divergence; the spec uses only patterns with in-suite prior art (arrow functions, contains operator, captured closure vars — all present in the existing cli/ and auth/ specs). No mutation-through-closure, no bracket-notation calls, no constructor-named-arg closures.
  • Security: no change to password handling — the reload-password preservation logic is pre-existing #3030 behavior, untouched by the rename.

@bpamiri

bpamiri commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Superseded by #3057 (merged ff65858) — same local.url rename across all four Application.cfc copies, with parity-spec pins and live Adobe red→green verification; #3071 (merged f84bf8c) subsequently fixed the adjacent case-sensitivity bug in the same gate.

@bpamiri bpamiri closed this Jun 12, 2026
@bpamiri
bpamiri deleted the fix/bot-3053-adobe-cf-every-password-reload-and-url-env-switch branch June 12, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant