#21173: fix(config): support envFiles option in resolveConfigEnvVars for gateway-only secrets
size: S
## Summary
Fixes #21130.
When a secret is injected into the `openclaw-gateway` systemd service via `EnvironmentFile=`, the CLI process does **not** inherit that variable — so every CLI command that reads `openclaw.json` throws `MissingEnvVarError`, even though the gateway itself works fine.
## Root cause
`resolveConfigEnvVars` always substitutes against `process.env`. There is no way to pass additional env sources, so gateway-only secrets are invisible to the CLI.
## Fix
Adds two exports to `src/config/env-substitution.ts`:
### `parseEnvFile(content: string): Record<string, string>`
Parses systemd-style `KEY=VALUE` env files:
- Skips comment lines (`#`)
- Handles quoted values (`"val"` / `'val'`)
- Strips optional `export` prefix
- Only accepts uppercase variable names (same pattern as existing substitution)
### `ResolveConfigEnvVarsOptions`
```ts
interface ResolveConfigEnvVarsOptions {
env?: NodeJS.ProcessEnv; // base env (defaults to process.env)
envFiles?: string[]; // additional env-file contents to merge
}
```
### Updated `resolveConfigEnvVars` signature (fully backward compatible)
```ts
// Legacy call — unchanged behaviour
resolveConfigEnvVars(obj, process.env)
// New call — merge gateway secrets file before substitution
const fileContent = fs.readFileSync('/etc/openclaw/secrets/ai-assistant.env', 'utf8');
resolveConfigEnvVars(obj, { envFiles: [fileContent] });
```
Values in `envFiles` override `env`; later entries override earlier ones. The existing `NodeJS.ProcessEnv` overload is detected by the absence of `env`/`envFiles` keys and routes to the old code path unchanged.
## What did NOT change
- `substituteString` / `substituteAny` internals are unchanged
- All existing tests continue to pass
- `MissingEnvVarError` still throws for truly absent vars
- No new dependencies
## Test coverage
Existing 30+ tests in `env-substitution.test.ts` cover all existing paths. New `parseEnvFile` is self-contained and can be tested with:
```ts
parseEnvFile('GITHUB_PAT=ghp_xxx\n# comment\nFOO="bar baz"')
// → { GITHUB_PAT: 'ghp_xxx', FOO: 'bar baz' }
```
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Extends config substitution to support systemd file content parsing via new `parseEnvFile()` function and `envFiles` option.
**Changes**
- `parseEnvFile()` handles systemd format with comments and quotes
- New `ResolveConfigEnvVarsOptions` interface for flexible sources
- Maintains backward compatible signature
**Issues**
- Quote stripping bug for single-character values
- Type detection breaks with certain object keys
<h3>Confidence Score: 2/5</h3>
- This PR has critical bugs that will cause incorrect behavior in edge cases
- Two logic bugs found: (1) `parseEnvFile` incorrectly handles single-character quoted values, producing empty strings instead of the literal character, (2) backward compatibility check fails when legacy env objects contain `env` or `envFiles` keys, misidentifying them as new options format
- src/config/env-substitution.ts requires fixes for quote handling and type detection
<sub>Last reviewed commit: db6805e</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21164: feat(config): add lenient mode to resolveConfigEnvVars — preserve g...
by Mellowambience · 2026-02-19
88.9%
#10258: fix(config): preserve ${ENV_VAR} references when writing config (#9...
by nu-gui · 2026-02-06
82.1%
#18498: daemon: load systemd EnvironmentFile and drop-ins so gateway status...
by saurav470 · 2026-02-16
77.2%
#15983: feat(exec): support env field in tools.exec config
by Imccccc · 2026-02-14
77.1%
#9747: feat(config): add pass (password-store) secret backend support
by evilbuck · 2026-02-05
76.4%
#8139: fix(config): block dangerous environment variables from config.env
by yubrew · 2026-02-03
75.4%
#9200: Fix: Strip dangerous env vars from baseEnv in host execution
by vishaltandale00 · 2026-02-05
75.4%
#21459: fix(gateway): resolve port from profile config, not inherited env
by kkeeling · 2026-02-19
74.3%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
74.1%
#5823: fix(config): exit cleanly on invalid config instead of high CPU loop
by gavinbmoore · 2026-02-01
73.7%