fix: cross-section substitution resolves to empty when no factors match#3810
Merged
gaborbernat merged 7 commits intotox-dev:mainfrom Feb 23, 2026
Merged
Conversation
When {[section]key} references a value with factor-conditional lines and
no factors match, the substitution should resolve to empty string rather
than remaining unresolved as a literal reference.
The root cause: process_raw() raises KeyError when factor filtering empties
a value that originally had content (added in tox-dev#3751 to fix tox-dev#3189). For
same-section config values this is correct — it allows fallback to computed
defaults (e.g. base_python derived from env name). But for cross-section
references via SectionProxy, the KeyError propagated all the way up to
the replacer, which returned None (unresolved), leaving the literal
{[section]key} in the output.
The fix: when a SectionProxy lookup raises KeyError but the key exists in
the section, the factor filtering emptied the value — return empty string
instead of propagating the error. This preserves the tox-dev#3189 fix for
same-section config values while correctly handling cross-section references.
Fixes tox-dev#3809
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
for more information, see https://pre-commit.ci
Move SectionProxy resolution logic into a helper method to stay within ruff's branch count and nesting depth limits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
for more information, see https://pre-commit.ci
gaborbernat
requested changes
Feb 23, 2026
Consistent with the multiline style requested in review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
auto-merge was automatically disabled
February 23, 2026 14:51
Head branch was pushed to by a user without write access
gaborbernat
approved these changes
Feb 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #3809
Cross-section substitution
{[section]key}now correctly resolves to empty string when the referenced value contains only factor-conditional lines and no factors match the requesting environment.Before:
{[devpisettings]storagebackend}remained as a literal unresolved reference when no factors matched.After: Resolves to empty string, matching pre-4.39.0 behavior.
Root cause
PR #3751 (fixing #3189) added a
KeyErrorinprocess_raw()when factor filtering empties a value that originally had content. This correctly causes same-section config values (likebase_python) to fall through to their computed defaults. However, for cross-section references loaded viaSectionProxy, theKeyErrorpropagated to the replacer which returnedNone(unresolved), leaving the literal{[section]key}in output.Fix
In
ReplaceReferenceIni.__call__(), when aSectionProxylookup raisesKeyErrorbut the key exists in the section, return empty string instead of propagating the error. This preserves the #3189 fix while correctly handling cross-section references.Test plan
test_cross_section_factor_conditional_resolves_to_emptytest_factor_conditional_falls_back_to_default(base_pythonsettings may breakignore_base_python_conflict = falsebehaviour #3189 regression test) passes🤖 Generated with Claude Code