Warn loudly when config-shaping env vars arrive via component .env files (not honored)#1580
Conversation
… composition (#1513) HARPER_CONFIG / HARPER_SET_CONFIG / HARPER_DEFAULT_CONFIG delivered via the documented loadEnv (.env) mechanism landed after getConfigObj() composed and memoized the config, so they silently no-oped (e.g. mcp.application via .env never mounted /mcp). A pre-pass in initConfig now scans componentsRoot + RUN_HDB_APP for loadEnv declarations and applies only those three vars to process.env before the single applyRuntimeEnvVarConfig call, mirroring the loader's config-filename precedence, pattern validation, and override precedence. loadEnv now skips identical-value conflicts silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The integration test fixture is meaningless without the .env that delivers HARPER_CONFIG. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a pre-pass mechanism (config/componentEnvPrepass.ts) to parse and apply config-shaping environment variables from component .env files to process.env before the root configuration is composed and memoized, resolving issue #1513. It also includes corresponding integration and unit tests, and updates conflict handling in resources/loadEnv.ts. The reviewer identified a high-severity issue where relative rootPath configurations could resolve incorrectly relative to the current working directory instead of the HarperDB base path, and provided a code suggestion to use env.getHdbBasePath() during resolution.
Gemini review: a relative rootPath in the config doc resolved against process.cwd(); the config file's dirname is the authoritative root (it is how loadRootComponents locates the root component too). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kriszyp
left a comment
There was a problem hiding this comment.
Question on the overall design direction here: do we really want components to be able to alter top-level config? My instinct is the opposite direction — the top-level config should control components, not vice versa. Letting a component's .env (via loadEnv) shape HARPER_CONFIG/HARPER_SET_CONFIG/HARPER_DEFAULT_CONFIG on the composed config feels like it inverts that relationship: a component can now reach up and mutate instance-wide config that other components and the top-level config file also depend on.
I get that this closes a real gap (#1513 — these vars silently no-op today if only delivered via a component's .env), but I'd like to understand the motivating use case a bit more before this direction gets locked in. Is there a reason a component needs to shape top-level config rather than just consuming its own scoped config?
|
Good question — context: this addresses #1513, filed by Nathan's agent (tps-flint), which hit the sharp edge directly: it delivered Totally understandable that we don't want components inverting the config relationship — top-level config controlling components is the right direction, and honoring these vars from a component Proposal: pivot this PR to warn-don't-honor. Keep the early detection of Comment drafted by an LLM (Claude Fable 5) on Kyle's direction. |
Love it! Do it! |
…ot applied Per review direction (kriszyp): top-level config controls components, not vice versa, so a component .env must not shape HARPER_*CONFIG. What #1513 actually needed fixed was the silence. The boot pre-pass now warns (actionable: names the variable, file, and supported channels) instead of applying, and loadEnv warns at component-load time too, covering components deployed after boot. Verified live: both warnings fire and /mcp stays unmounted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pivoted in 752cdd7: detection stays, application is gone — the pre-pass and loadEnv now emit actionable warnings (variable + file + supported channels) and |
kriszyp
left a comment
There was a problem hiding this comment.
Solid diagnostic feature — correctly reverses the original approach (warn instead of silently applying) after review feedback, careful about never throwing, and doesn't leak env-var values into the warning (only names + file paths, which matters since HARPER_CONFIG/HARPER_SET_CONFIG payloads can carry secrets). Well tested at both the unit and integration level — the integration test actually boots a real instance with a fixture component and asserts the not-honored config doesn't take effect, not just that a log line fires.
One drift risk worth resolving before merge: CONFIG_SHAPING_ENV_VARS in componentEnvPrepass.ts is a second, independently-maintained list of the same three vars already named (as the ConfigSource type union) in harperConfigEnvVars.ts. There's no shared export today, so this isn't a trivial one-line fix — but it's exactly the failure mode this PR exists to eliminate: if a fourth config-shaping var is ever added to harperConfigEnvVars.ts, nothing forces this list to follow, and the new var would silently regress to the pre-#1513 behavior (no warning, no visibility) for anyone shipping it via .env. Would you take exporting a shared array from harperConfigEnvVars.ts (or at minimum a comment pointing back at this list) as part of this PR, or would a tracked follow-up be fine?
Also worth a quick confirmation: initConfig(force = true) re-runs this whole block since it's gated on flatConfigObj === undefined || force, not a separate one-time flag. Is initConfig(true) ever invoked mid-process (a config-reload path)? applyRuntimeEnvVarConfig right below it is explicitly documented as unsafe to run twice — since the pre-pass is stateless (scan + warn, no process.env writes) I don't think it shares that constraint, but a one-line confirmation would close the loop.
|
Cross-PR coordination with #1726 (#1618 — The boot pre-pass correctly never touches That's harmless for this PR as-is — config is composed/memoized before loadEnv, so nothing re-reads it. But #1726 changes exactly that: it re-composes from Suggestion: add a @kriszyp — since you set the top-down direction: does enforce-at-injection here match your intent, or would you rather each consumer defend itself (e.g. #1726 composing from a boot snapshot of Comment generated by an LLM (Claude Opus 4.8, 1M context). |
…ng vars The warning branch fell through to the process.env assignment, so a component .env still landed the HARPER_*CONFIG trio in the process environment — harmless while config is composed-then-memoized, but a consumer that re-composes from process.env (#1618/#1726) would then honor it, silently re-inverting the top-down relationship. Warn AND skip; test asserts the trio never reaches process.env even with loadEnv override enabled (heskew review). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@heskew Good catch — fixed in 7b05a84, enforce-at-injection as you leaned: the config-shaping branch now warns and Comment generated by an LLM (Claude Fable 5). |
Patch cherry-pick: conflictCherry-pick onto The conflict markers are committed on branch |
…eality Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Warn loudly when config-shaping env vars arrive via component .env files (not honored)
Fixes #1513 — pivoted to warn-don't-honor per the design discussion below (top-level config controls components, not vice versa).
Summary
HARPER_DEFAULT_CONFIG/HARPER_CONFIG/HARPER_SET_CONFIGdelivered via a component's.env(loadEnv) silently no-op'd: the config is composed and memoized before components load. The original PR applied them in a pre-composition pass; review (kriszyp) correctly flagged that as inverting the config relationship. The pivot keeps the detection and fixes the actual pain — the silence:componentEnvPrepass.ts): the same component/.envscan (loadEnvfilesglobs, config-file precedence, escape protection) now emits an actionable warning per config-shaping var found — naming the variable, the file, and the supported channels (process environment /harper-config.yaml) — without touchingprocess.env.Where to look
Testing
10 unit tests on the pre-pass (detection reach: globs, config-file precedence, hidden dirs, escape protection; warns-without-applying incl. when a real process env var coexists; encrypted values) + loadEnv warning test + integration test flipped: the fixture delivers
HARPER_CONFIGonly via.envand asserts/mcpstays unmounted. Verified live on a real instance: both warnings appear in the boot log and the config is not shaped.Generated by an LLM (Claude Fable 5); pivot direction approved by kriszyp on this PR.