# How Environment Variable Expansion Works in Reasonix TOML Configurations

> Discover how Reasonix TOML configurations expand environment variables using shell-style placeholders and .env file overrides. Learn about its templating engine and immutable plugin handling.

- Repository: [YHH/DeepSeek-Reasonix](https://github.com/esengine/DeepSeek-Reasonix)
- Tags: internals
- Published: 2026-06-23

---

**Reasonix treats [`reasonix.toml`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/reasonix.toml) or processing plugin definitions, the engine resolves `${...}` syntax through a multi-stage pipeline defined in [`internal/config/expand.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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.

```go
// 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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`

```go
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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/internal/installsource/install_source.go)).
- **Interactive CLI**: The chat TUI interface applies expansion at line 158 of [`internal/cli/chat_tui.go`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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`](https://github.com/esengine/DeepSeek-Reasonix/blob/main/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.