How Agent Reach's Configuration System Manages YAML Storage and Environment Variable Overrides
Agent Reach stores user-level settings in a private YAML file at ~/.agent-reach/config.yaml, with environment variables providing automatic overrides when YAML keys are absent.
The Config class in agent_reach/config.py implements a secure, layered configuration system that prioritizes file-based persistence while allowing flexible runtime overrides. This design lets developers commit non-sensitive defaults to version control while injecting secrets through environment variables in CI/CD or containerized deployments.
YAML File Structure and Location
Agent Reach uses a dedicated hidden directory in the user's home folder for all configuration storage.
# From agent_reach/config.py (lines 1-3)
CONFIG_DIR = Path.home() / ".agent-reach"
CONFIG_FILE = CONFIG_DIR / "config.yaml"
The directory and file are created on first use with strict permissions, ensuring no other users on the system can read or write configuration data.
Secure File Operations
Symlink Attack Prevention
Before any read or write operation, the configuration system validates that no component of the path is a symbolic link. This prevents attackers from redirecting credential reads to malicious files.
# From agent_reach/config.py (lines 38-43)
def _reject_symlink(self, path: Path) -> None:
"""Raise ConfigSecurityError if *path* or any parent is a symlink."""
ensure_no_symlink_path(path) # Defined in agent_reach/utils/paths.py
The helper ensure_no_symlink_path traverses the entire path hierarchy, checking Path.is_symlink() at each level.
Atomic YAML Writes
Configuration updates use an atomic write pattern to prevent data corruption if the process crashes mid-operation.
# Simplified from agent_reach/config.py (lines 45-79)
def _atomic_write_yaml(self, data: dict) -> None:
tmp_path = self.CONFIG_FILE.with_suffix(".tmp")
with open(tmp_path, "w") as f:
yaml.safe_dump(data, f)
f.flush()
os.fsync(f.fileno()) # Ensure data reaches physical storage
os.replace(tmp_path, self.CONFIG_FILE) # Atomic rename on POSIX/Windows
# Restrict to owner-only access (0o600)
self.CONFIG_FILE.chmod(stat.S_IRUSR | stat.S_IWUSR)
This pattern guarantees that the file is never in a partially-written state. The fsync call ensures durability even if the operating system crashes immediately after the operation.
Loading and Caching Mechanism
The load() method reads configuration with size limits and symlink protection.
# From agent_reach/config.py (lines 31-50)
def load(self) -> None:
self._reject_symlink(self.CONFIG_FILE)
if not self.CONFIG_FILE.exists():
self.data = {}
return
content = read_small_text_no_follow(self.CONFIG_FILE, max_bytes=1_048_576)
self.data = yaml.safe_load(content) or {}
The read_small_text_no_follow utility (from agent_reach/utils/paths.py) enforces a 1 MiB maximum, preventing memory exhaustion from malformed or malicious files.
Environment Variable Override System
The get() method implements a two-tier lookup: YAML values take precedence, with environment variables as fallback.
# From agent_reach/config.py (lines 58-66)
def get(self, key: str, default: Any = None) -> Any:
# First: check the in-memory YAML data
if key in self.data:
return self.data[key]
# Second: check uppercase environment variable
env_val = os.environ.get(key.upper())
if env_val is not None:
return env_val
return default
This design choice—YAML over environment—means that explicit file-based configuration always wins, while environment variables provide convenient defaults. The uppercase transformation (key.upper()) creates a predictable mapping: a config key openai_api_key checks OPENAI_API_KEY.
Practical Override Example
from agent_reach.config import Config
import os
cfg = Config()
# Set in YAML via previous save()
cfg.set("api_timeout", 30)
# Environment variable is ignored while YAML value exists
os.environ["API_TIMEOUT"] = "60"
print(cfg.get("api_timeout")) # → 30
# Delete from YAML to activate environment fallback
cfg.delete("api_timeout")
print(cfg.get("api_timeout")) # → "60"
Read-Only Mode for Safe Access
Configuration instances can be locked to prevent accidental modifications.
# From agent_reach/config.py (lines 51-58)
def __init__(self, read_only: bool = False):
self.read_only = read_only
self.load()
def save(self) -> None:
if self.read_only:
raise ConfigReadOnlyError("Cannot save in read-only mode")
# ... atomic write logic
This is useful when passing configuration to untrusted code or background workers that should not persist changes.
Feature Gating with Configuration Validation
Agent Reach uses a declarative system to check whether required configuration exists for optional features.
# From agent_reach/config.py (lines 5-12)
FEATURE_REQUIREMENTS = {
"github": ["github_token"],
"openai": ["openai_api_key", "openai_organization"],
"anthropic": ["anthropic_api_key"],
}
The is_configured() method leverages the same get() logic, respecting both YAML and environment sources.
# From agent_reach/config.py (lines 99-102)
def is_configured(self, feature: str) -> bool:
required = FEATURE_REQUIREMENTS.get(feature, [])
return all(self.get(key) is not None for key in required)
Masked Export for Safe Logging
The to_dict() method redacts sensitive values to prevent credential leaks.
# From agent_reach/config.py (lines 110-132)
def to_dict(self) -> dict:
result = {}
for key, value in self.data.items():
if any(s in key.lower() for s in ("key", "token", "secret", "password")):
result[key] = value[:4] + "..." if isinstance(value, str) else "***"
else:
result[key] = value
return result
A value like ghp_xxxxxxxxxxxxxxxxxxxx becomes ghp..., sufficient for debugging without exposing credentials.
Complete Usage Example
from agent_reach.config import Config
import os
# Initialize with default path ~/.agent-reach/config.yaml
cfg = Config()
# Persist a setting (atomic write with 0o600 permissions)
cfg.set("github_token", "ghp_xxxxxxxxxxxxxxxxxxxx")
# Override via environment for CI pipeline
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-xxx"
# Retrieve with automatic fallback chain
github_token = cfg.get("github_token") # From YAML
anthropic_key = cfg.get("anthropic_api_key") # From env (not in YAML)
# Check feature readiness
if cfg.is_configured("github"):
print("GitHub integration ready")
# Safe export for logs
print(cfg.to_dict()) # {"github_token": "ghp...", ...}
Summary
- File location:
~/.agent-reach/config.yamlwith owner-only permissions (0o600) - Security: Symlink rejection via
ensure_no_symlink_pathinagent_reach/utils/paths.py - Atomicity:
_atomic_write_yamluses temp file + fsync + rename pattern - Override precedence: YAML values override environment variables; keys are matched case-insensitively via uppercase transformation
- Safety features: Read-only mode, masked export, and 1 MiB read limits prevent common configuration vulnerabilities
Frequently Asked Questions
Can I change the configuration file location?
No, the path is hardcoded to ~/.agent-reach/config.yaml via Path.home() constants. This design prevents path confusion attacks and ensures consistent behavior across the codebase. For multi-environment deployments, use environment variable overrides rather than multiple config files.
Why does YAML take precedence over environment variables?
This prioritization allows users to temporarily lock a value by persisting it, even when an environment variable exists. It also prevents surprise behavior when environment variables are set system-wide but a user has explicitly configured a different value.
How does Agent Reach prevent credential leaks in crash reports?
The to_dict() method automatically masks any key containing key, token, secret, or password substrings. Additionally, __repr__ is not overridden to expose raw data, and the Config class does not implement direct dict access—developers must explicitly call get() or to_dict().
What happens if the config file is corrupted?
yaml.safe_load raises a yaml.error.YAMLError on parse failure, which propagates uncaught in load(). The calling code should wrap configuration initialization in appropriate exception handling. Empty or missing files default to an empty dictionary without error.
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 →