Skip to content

Fix Admin Settings 500: Jinja set scope does not cross an include - #1322

Open
Paul Lizer (paullizer) wants to merge 1 commit into
feature/admin-settings-iafrom
admin-ia-fix-pane-scope
Open

Fix Admin Settings 500: Jinja set scope does not cross an include#1322
Paul Lizer (paullizer) wants to merge 1 commit into
feature/admin-settings-iafrom
admin-ia-fix-pane-scope

Conversation

@paullizer

Copy link
Copy Markdown
Contributor

Fixes the 500 on Admin Settings, plus a silent variant of the same bug found while investigating.

Base: feature/admin-settings-ia.

The failure

File "templates/admin/_panes/actions.html", line 17, in top-level template code
<input ... {% if analyze_capability.enabled %}checked{% endif %}>
jinja2.exceptions.UndefinedError: 'analyze_capability' is undefined

Root cause

Jinja {% set %} scope does not cross an {% include %} boundary.

While Admin Settings was one 14,000-line template this never mattered — a variable derived near the top was visible everywhere below it. Splitting it into 44 per-tab partials made it matter: a variable derived in one partial is simply not visible in another.

Three variables were left behind when their consuming card moved to a different tab:

VariableDeclared inUsed inFailure
analyze_capabilityagentsactions500
comparison_capabilityagentsactions500
enable_dai_debugredis-cachingcosmossilent

Each is a pure derivation from settings, which the route supplies to every template, so moving the declaration into the consuming pane is safe and self-contained.

The one you didn't see

The two failure modes are very different, and the quiet one is worse:

  • Attribute access on a missing name raises and takes the page down. Loud, found immediately.
  • A boolean test on a missing name is silently false, because Jinja's default Undefined is falsy.

enable_dai_debug is used as {% if enable_dai_debug %} in six places on the Cosmos tab. Those debug controls and shadow-validation diagnostics would never have rendered, whatever the setting was, and nothing would have reported a problem. It was found only by auditing for the pattern that caused the crash — not by the error.

Why every existing test passed

All the Admin Settings tests inspect the template as text: field names, card ids, tag balance, navigation parity, modal placement. Every one was green.

None of them execute the template. A template can satisfy every static check and still raise the moment Flask renders it. That is the real gap this PR closes.

test_admin_settings_renders.py

Renders the whole page through Jinja using the same undefined handling Flask uses, so an UndefinedError surfaces in the suite instead of in a browser. This is the test that would have caught the bug immediately.

It also asserts every navigation tab renders a pane, and that exactly one pane is active and it is the landing tab.

The render context is derived from the route's own render_template call and the app context processors, so it cannot drift as those change.

test_admin_settings_pane_variable_scope.py

Static scope analysis via jinja2.meta.find_undeclared_variables, which catches the silent variant a render cannot. It names the offender precisely:

'analyze_capability' is used in 'actions' but only declared in ['agents']

Variables declared inside a {% for %} loop are correctly treated as local.

Both tests are verified against a planted copy of the real bug, so they are known to fail when they should.

Verification

CheckResult
Page renders1,383,741 chars, no error
Navigation tabs rendering a pane44 / 44
Active panesexactly 1, and it is the landing tab
Cross-pane variable leaksnone
Field names vs Development452 → 452, zero lost, zero added
Regression set (75 files)32 pre-existing failures, unchanged
Jinja compile46/46 admin templates
XSS sinkspass

No setting changed and nothing needs re-entering.

Version 0.260.019. Full write-up in ADMIN_SETTINGS_PANE_VARIABLE_SCOPE_FIX.md.

The rule worth keeping: a template is not verified until it has been rendered.

Visiting Admin Settings raised UndefinedError on analyze_capability and
returned a 500 for every request.
Root cause
Jinja {% set %} scope does not cross an {% include %} boundary. While
Admin Settings was one 14,000 line template that never mattered: a
variable derived near the top was visible everywhere below it. The
information architecture rework split it into 44 per-tab partials, and
a variable derived in one partial is not visible in another.
Three variables were left behind when their consuming card moved:
analyze_capability agents -> actions 500 error
comparison_capability agents -> actions 500 error
enable_dai_debug redis -> cosmos silent
Two failure modes, and the quiet one is worse
Attribute access on a missing name raises and takes the page down.
Loud, and found immediately.
A boolean test on a missing name is silently false, because Jinja's
default Undefined is falsy. Nothing errors. The Cosmos tab's debug
controls and shadow validation diagnostics would never have rendered,
whatever the setting was, and nothing would have reported a problem.
That one was found only by auditing for the pattern that caused the
crash.
Each declaration is a pure derivation from settings, which the route
supplies to every template, so moving it to the consuming pane is safe.
Why the existing tests missed it
Every Admin Settings test inspects the template as text: field names,
card ids, tag balance, navigation parity, modal placement. All of them
passed, because none of them execute the template.
test_admin_settings_renders.py now renders the whole page through
Jinja with the same undefined handling Flask uses, so an
UndefinedError surfaces in the suite instead of in a browser. It also
asserts every navigation tab renders a pane and that exactly one pane
is active and it is the landing tab. The context is derived from the
route's own render_template call and the app context processors, so it
cannot drift.
test_admin_settings_pane_variable_scope.py adds static scope analysis
via jinja2.meta, which catches the silent variant a render cannot,
and names the offender precisely:
'analyze_capability' is used in 'actions' but only declared in ['agents']
Both are verified against a planted copy of the real bug.
The rule: a template is not verified until it has been rendered.
Verified
page renders 1,383,741 chars, no error
tabs -> panes 44 / 44
active panes exactly 1, and it is the landing tab
field names 452 on Development, 452 here, none lost, none added
regression set 32 failures, identical to baseline
jinja compile 46/46 admin templates
xss sinks pass
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paullizer