click-clirunner-env-none-deletes

Installation
SKILL.md

Click CliRunner: env=None deletes; absence does NOT

Problem

You wrote a CLI test where the SUT (system under test) branches on whether an env var is set. The test invokes the CLI via CliRunner.invoke(cli, args, env=clean_env_dict), where clean_env_dict omits the env var you want absent. The test passes locally. You ship it. Later it turns out the test was never exercising the "var absent" branch — it was running with the var present (inherited from your shell), and your assertion happened to also pass for the "var present" code path. In a live-API-call test, this can mean the test SILENTLY makes the network call you thought you were preventing.

Root cause

Click's CliRunner.invoke(env=...) is an OVERRIDE dict, not a REPLACEMENT environment. It iterates the dict and applies each key:

  • env[key] = "value" → sets os.environ[key] = "value" for the duration of the invocation, restoring afterward
  • env[key] = None → calls del os.environ[key] for the duration of the invocation, restoring afterward
  • key absent from envos.environ[key] is left UNTOUCHED

Originally verified against click/testing.py:534 on Click 8.1.x; re-checked 2026-08-23 and 2026-08-24 against the current published testing module source. The durable evidence is the signature: env: Mapping[str, str | None] | None on CliRunner, CliRunner.invoke and CliRunner.isolation. A value type of str | None is the API stating that None is a meaningful value rather than an omission — that is the delete. The docs describe env as "overrides", which is the absent-keys-untouched half. Cite the signature, not a file offset or a version pin — the 8.1.x branch no longer exists, and the signature has survived every check so far.

Trigger conditions

You wrote one of these patterns and the test silently passed:

Installs
1
First Seen
7 days ago
click-clirunner-env-none-deletes — mrbinnacle/skills