How Environment Variable Expansion Works in Reasonix TOML Configurations
Reasonix treats reasonix.toml as a template that expands shell-style ${VAR} and ${VAR:-default} placeholders using a regex-based parser, supports project-specific .env file overrides, and guarantees immutability by deep-copying plugin configurations before expansion.
Reasonix, the configuration engine powering the DeepSeek-Reasonix ecosystem, implements a robust environment variable interpolation system for its TOML configuration files. When loading reasonix.toml or processing plugin definitions, the engine resolves ${...} syntax through a multi-stage pipeline defined in internal/config/expand.go. This ensures that sensitive credentials, API endpoints, and version flags can be injected at runtime without hardcoding values into configuration files.
Expansion Syntax and Patterns
The expansion engine recognizes shell-style variable references using a regular expression defined as varRef in internal/config/expand.go (lines 9-12). The parser supports two distinct syntax patterns:
${VAR}– Expands to the value of environment variableVAR, or an empty string if unset.${VAR:-default}– Expands to the value ofVARif present and non-empty; otherwise uses the specified fallback string.
This syntax mirrors POSIX parameter expansion, making it intuitive for operators familiar with shell scripting.
The Expansion Pipeline
The core logic resides in the expandVarsWithLookup function, which processes strings through three discrete stages to resolve variable references.
Stage 1: Parsing Variable References
The regex engine scans input strings for ${...} sequences. When encountering a reference like ${API_KEY} or ${VERSION:-v1}, the parser extracts the variable name and optional default value. This matching occurs at internal/config/expand.go lines 9-12, where the pattern is compiled once and reused across all expansion operations.
Stage 2: Environment Lookup Strategy
The lookup behavior depends on the context in which expansion is invoked. The generic expandVarsWithLookup function accepts a closure that determines how variable values are resolved:
-
Default Lookup (
ExpandVars): Reads from the operating system environment usingos.LookupEnv. Critically, this implementation ignores empty values—a variable set to an empty string is treated as if it were unset. This logic appears at lines 15-23 ofinternal/config/expand.go. -
Scoped Lookup (
scopedEnvLookup): When loading project-specific configurations, Reasonix first checks the system environment, then falls back to values loaded from a local.envfile. This two-tier lookup at lines 43-52 allows repository-specific overrides without polluting the global environment.
Stage 3: Replacement and Fallback Handling
For each matched reference, the engine performs replacement according to these rules (implemented at lines 30-40):
- If the variable exists and has a non-empty value, substitute that value.
- If the variable is missing or empty and a fallback was specified (
${VAR:-default}), substitute the fallback string. - Otherwise, substitute an empty string.
This ensures predictable behavior where explicit defaults provide safety nets for optional configuration parameters.
Scoped Environment Support for .env Files
Reasonix supports project-local environment files through the scopedEnvLookup mechanism. When Config.expandVars (lines 55-60) processes a configuration, it creates a lookup closure that prioritizes the process environment but respects values defined in the project's .env file.
// Example: Project-specific .env integration
cfg, _ := config.LoadForEdit("reasonix.toml")
// If .env contains API_KEY=sk-xyz, this resolves to the file value
url := cfg.expandVars("https://service/${API_KEY}")
This design allows teams to commit template configurations to version control while keeping secrets in untracked .env files.
Plugin Configuration Expansion
The ExpandedPlugin method (lines 62-79 of internal/config/expand.go) demonstrates Reasonix's commitment to immutability. Rather than modifying plugin definitions in place, the method creates a deep copy of the PluginEntry struct and runs expansion on specific fields:
CommandURLArgsEnvHeaders
p := config.PluginEntry{
URL: "https://api/${SERVICE:-default}",
Args: []string{"--token", "${TOKEN}"},
Headers: map[string]string{"Authorization": "Bearer ${TOKEN}"},
}
expanded := p.ExpandedPlugin()
// expanded.URL == "https://api/default" (fallback used)
// expanded.Args[1] == value of $TOKEN or empty string
// Original `p` remains unchanged
This guarantees that the original configuration object stays pristine for potential re-expansion with different contexts or debugging purposes.
Integration Points in the Codebase
The expansion engine activates at multiple critical paths throughout the application:
- Boot Process: At
internal/boot/boot.goline 1279, the system callsExpandedPluginon plugin entries before execution, ensuring runtime commands are fully resolved. - Installation Sources: The installer uses
ExpandVarsdirectly when resolving paths from configuration (line 459 ofinternal/installsource/install_source.go). - Interactive CLI: The chat TUI interface applies expansion at line 158 of
internal/cli/chat_tui.goto resolve dynamic configuration values during interactive sessions.
Summary
- Reasonix implements POSIX-style variable expansion (
${VAR}and${VAR:-default}) via regex matching ininternal/config/expand.go. - The lookup strategy treats empty environment variables as unset and supports project-specific
.envfile overrides throughscopedEnvLookup. - Fallback values provide default configurations when variables are missing, using the
:-syntax. - Plugin configurations remain immutable through deep-copy expansion via the
ExpandedPluginmethod. - Expansion occurs during boot, installation, and CLI operations to ensure runtime values are resolved before execution.
Frequently Asked Questions
How does Reasonix handle empty environment variables?
According to the source code in internal/config/expand.go (lines 15-23), the default ExpandVars function treats empty values as equivalent to unset variables. If a variable is set to an empty string, the engine proceeds to use the fallback value or returns an empty string, just as it would if the variable were completely absent from the environment.
Can I use environment variables in plugin headers and arguments?
Yes. The ExpandedPlugin method (lines 62-79 of internal/config/expand.go) specifically expands variables within PluginEntry fields including Args, Headers, URL, Command, and Env. The method creates a deep copy of the plugin definition, ensuring that headers like Authorization: Bearer ${TOKEN} resolve correctly without modifying the original configuration object.
What happens if I don't provide a default value for an undefined variable?
If a variable reference lacks a fallback (e.g., ${UNDEFINED_VAR}) and the variable is not present in the environment or .env file, the expansion engine substitutes an empty string. As implemented at lines 30-40 of internal/config/expand.go, the replacement logic returns an empty string when no value is found and no :-default suffix is provided.
Does Reasonix support .env files for local configuration overrides?
Yes. The scopedEnvLookup function (lines 43-52) implements a two-tier lookup that checks the system environment first, then falls back to values loaded from a project-specific .env file. When using Config.expandVars (lines 55-60), Reasonix applies this scoped lookup, allowing variables defined in local .env files to take precedence over missing system environment variables.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →