ADR 0032: Categorize InvalidVariableNameError as a Suppressible Expansion Error¶
Status¶
Accepted
Date¶
2026-07-03
Context¶
ADR-0018 introduced two granular flags, stop_on_expansion_error and stop_on_resolution_error, and grouped failures by the operation that produces them. Its categorization named VariableNotFoundError as the only failure controlled by stop_on_expansion_error, with CircularReferenceError always raised.
InvalidVariableNameError (ADR-0002 hierarchy) is raised during variable expansion when a ${...} reference contains a name that is not a valid identifier. In practice this also fires for ${...} syntax that envresolve does not support, such as bash's alternate-value expansion ${VAR:+word}. The name part parsed out of such a reference is not a valid identifier, so expansion rejects it.
This failure mode is common with shell-provided environment variables that were never authored for envresolve. The default Debian/Ubuntu PS1 contains ${debian_chroot:+($debian_chroot)}. When such a variable is present in os.environ, resolving it raises InvalidVariableNameError.
Because InvalidVariableNameError belonged to neither category, it propagated unconditionally — even when stop_on_expansion_error=False was explicitly requested. This aborted the entire resolution loop over os.environ, defeating the exact use case that stop_on_expansion_error=False exists to serve: tolerating system variables that cannot be expanded.
Decision¶
Categorize InvalidVariableNameError as an expansion failure controlled by stop_on_expansion_error, alongside VariableNotFoundError.
- When
stop_on_expansion_error=False, a variable whose value triggersInvalidVariableNameErroris skipped and the loop continues. - When
stop_on_expansion_error=True(the default), the error is wrapped with context, consistent with howVariableNotFoundErroris reported.
CircularReferenceError remains always raised and is unaffected by this decision.
Rationale¶
Operation-based grouping (ADR-0018): The error occurs during the expansion operation, so by ADR-0018's own principle of grouping failures by operation it belongs to the expansion category. ADR-0018 anticipated that new error types would fit into the existing categories; this applies that intent.
Same real-world motivation as VariableNotFoundError: Both are triggered by system/shell variables (such as PS1) that were never intended as envresolve input. The motivating use case for stop_on_expansion_error=False in ADR-0018 was exactly these system variables.
Not a configuration error like CircularReferenceError: A circular reference can never resolve and is always an authoring mistake, which justifies always raising it. An unsupported or malformed ${...} reference inside a value the user does not control is safe to skip when leniency is explicitly requested.
Strict by default is preserved: The default remains True, so genuine typos in user-authored .env files (for example ${:-default} or ${1VAR}) still surface immediately. Only explicit opt-in leniency skips them.
Implications¶
Positive Implications¶
- The
resolve_os_environ/load_envleniency contract now holds for real-world environments: exporting a variable such asPS1no longer aborts resolution whenstop_on_expansion_error=False. - Under the default strict behavior,
InvalidVariableNameErroris now wrapped inEnvironmentVariableResolutionErrorwith the offending key as context, matchingVariableNotFoundErrorinstead of leaking a raw, context-free exception. - ADR-0018's categorization is broadened:
stop_on_expansion_errornow controlsVariableNotFoundErrorandInvalidVariableNameError.
Concerns¶
Malformed user input can be silently skipped: With stop_on_expansion_error=False, a genuine typo that produces an invalid name is skipped rather than reported.
Mitigation: This matches the existing trade-off already accepted for VariableNotFoundError. The strict default catches these cases; leniency is an explicit opt-in.
Alternatives¶
Keep InvalidVariableNameError always raised (like CircularReferenceError)¶
- Pros: No categorization change; malformed references always surface.
- Cons:
stop_on_expansion_error=Falsecannot tolerate common system variables (PS1), which contradicts the purpose of the flag; a single unsupported value aborts the whole loop. - Rejection reason: Unlike a circular reference, this failure is neither unresolvable-by-definition nor necessarily user-authored, and always raising it breaks the documented leniency use case.
Add a dedicated stop_on_invalid_name_error flag¶
- Pros: Maximum control over this specific failure.
- Cons: Grows the API with each new error type and reintroduces the per-exception-type flags that ADR-0018 already rejected.
- Rejection reason: ADR-0018 deliberately groups by operation for a stable, intuitive API.
References¶
- ADR-0002: Custom Exception Hierarchy
- ADR-0018: Granular Error Handling for Variable Expansion and Secret Resolution
- ADR-0024: Core Design Principle of "Fine-Grained Control"
- ADR-0026: Strategy for Exception Handling and Error Reporting
- ADR-0031: Support Default Values in Variable Expansion (
${VAR:-default}Syntax)