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 variable VAR, or an empty string if unset.
  • ${VAR:-default} – Expands to the value of VAR if 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 using os.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 of internal/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 .env file. 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):

  1. If the variable exists and has a non-empty value, substitute that value.
  2. If the variable is missing or empty and a fallback was specified (${VAR:-default}), substitute the fallback string.
  3. 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:

  • Command
  • URL
  • Args
  • Env
  • Headers
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.go line 1279, the system calls ExpandedPlugin on plugin entries before execution, ensuring runtime commands are fully resolved.
  • Installation Sources: The installer uses ExpandVars directly when resolving paths from configuration (line 459 of internal/installsource/install_source.go).
  • Interactive CLI: The chat TUI interface applies expansion at line 158 of internal/cli/chat_tui.go to resolve dynamic configuration values during interactive sessions.

Summary

  • Reasonix implements POSIX-style variable expansion (${VAR} and ${VAR:-default}) via regex matching in internal/config/expand.go.
  • The lookup strategy treats empty environment variables as unset and supports project-specific .env file overrides through scopedEnvLookup.
  • Fallback values provide default configurations when variables are missing, using the :- syntax.
  • Plugin configurations remain immutable through deep-copy expansion via the ExpandedPlugin method.
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →