post: An Empty Environment Variable Still Needs Validation_□×

An Empty Environment Variable Still Needs Validation

The logger handled an empty LOG_TIMESTAMP value by leaving the timestamp setting alone. The resulting log output was correct, but a required warning was missing.

The configuration contract had two parts: ignore an unrecognized value and tell the user it was ignored. The code did only the first.

Presence isn’t truthiness

The guard looked like this:

if (env.LOG_TIMESTAMP) {
  // Parse the setting.
}

An empty string is falsy in JavaScript, so it never reached the parser. An unset variable skipped the same branch.

For this configuration, those states needed different treatment. An absent variable meant there was no setting to apply. A present but empty variable was an unrecognized boolean value and needed a diagnostic.

An empty value can come from a config file, an expansion, or deployment tooling. The logger doesn’t need to guess how it happened. It needs to preserve the distinction long enough to apply its validation rules.

The fix checks whether the value is present:

if (env.LOG_TIMESTAMP !== undefined) {
  const timestamp = parseBooleanEnvironment(
    'LOG_TIMESTAMP',
    env.LOG_TIMESTAMP,
  );

  if (timestamp !== undefined) {
    config.timestamp = timestamp;
  }
}

The parser can now reject the empty string and emit its warning. A variable that isn’t set still produces no warning.

A truthiness check isn’t wrong in every configuration loader. It was wrong here because it bypassed behavior required for one of the possible inputs.

The warning was part of the result

A test that checked only the timestamp setting could pass before and after the fix. The empty value was ignored in both cases.

The missing assertion was on the diagnostic. The conformance runner needed to describe the input environment and the warnings expected from it, not just the resulting log record.

The opposite case mattered too: an unset variable should stay quiet. Warning about settings nobody supplied would make valid configurations noisy.

I want both sides covered. “Warn for an invalid value” and “don’t warn for an absent value” are separate expectations, even if the effective configuration is identical.

This doesn’t require a language comparison. Go and Rust offer APIs that distinguish presence from an empty value, but application code can still throw that information away. The relevant design choice is whether the loader preserves the distinction.

Control the environment around the test

A test for an unset variable can be affected by a developer’s shell. If LOG_LEVEL is already exported, the fixture isn’t starting from the state it describes.

For cases with an environment block, the runner clears the supported variables, applies the declared values, and restores the previous state afterward, including on failure.

Restoration is necessary, but it doesn’t make concurrent mutation of process-wide state safe. Tests sharing a process still need isolation or scheduling that prevents them from changing the same environment at once.

The fixture format gained another useful check: unknown keys fail instead of being silently ignored. A misspelled diagnostic expectation shouldn’t turn a test into a pass with no diagnostic assertion.

The output value was never enough to describe correctness here. The warning told the user that their supplied setting hadn’t been accepted. Omitting it left the configuration harder to understand, even though the logger fell back to a valid value.

Sources

I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].

START
llbbl.exeprojects/posts/experiments/subscribe.dlg
© 2026v1.0.0