python-env-var-rename-consistency
The bug class: a rename in the writer (install.sh) that silently breaks readers (nextcloud-bridge.py, boot-inject.sh) which still read the bare NEXTCLOUD_USER and fall back to the admin default — the installer authenticates as alice, the bridge as admin.
The fix: an identical ADMIN-first fallback chain at every reader (NEXTCLOUD_ADMIN_USER → NEXTCLOUD_USER → admin), with the test mirror's run_env_decision template + assertions updated in the same patch.
Patch 1 — install.sh (the rename + chain):
nextcloud_admin_user() {
if [[ -n "${NEXTCLOUD_ADMIN_USER:-}" ]]; then # new var wins
printf '%s\n' "${NEXTCLOUD_ADMIN_USER}"
elif [[ -n "${NEXTCLOUD_USER:-}" ]]; then # legacy fallback
printf '%s\n' "${NEXTCLOUD_USER}"
else
printf '%s\n' "admin" # default
fi
}
Patch 2 — nextcloud-bridge.py (same chain in Python):
def resolve_admin_user(env=None):
env = os.environ if env is None else env
if env.get("NEXTCLOUD_ADMIN_USER"): # 1. new name
return env["NEXTCLOUD_ADMIN_USER"]
if env.get("NEXTCLOUD_USER"): # 2. legacy, backward-compat
return env["NEXTCLOUD_USER"]
return DEFAULT_ADMIN_USER # 3. "admin"
Patch 3 — boot-inject.sh: identical [[ -n "${NEXTCLOUD_ADMIN_USER:-}" ]] / elif [[ -n "${NEXTCLOUD_USER:-}" ]] / else "admin" chain.
Patch 4 — tests/test_install_env_restore.py (lockstep mirror):
def run_env_decision(env):
"""Template mirroring the readers' env decision (ADMIN-first chain)."""
if env.get("NEXTCLOUD_ADMIN_USER"):
return env["NEXTCLOUD_ADMIN_USER"]
if env.get("NEXTCLOUD_USER"):
return env["NEXTCLOUD_USER"]
return DEFAULT_ADMIN
Assertions: ADMIN wins when both set; legacy honored when alone; default when neither; empty vars treated as unset; 5 env cases × 3 readers all match the template; plus grep canary tests — every reader must mention NEXTCLOUD_ADMIN_USER and it must appear before NEXTCLOUD_USER (word-boundary regex so ADMIN_ prefix isn't confused):
def test_every_reader_mentions_admin_var(path):
assert re.search(r"\bNEXTCLOUD_ADMIN_USER\b", path.read_text())
def test_admin_var_precedes_legacy_var(path):
assert re.search(r"\bNEXTCLOUD_ADMIN_USER\b", t).start() < \
re.search(r"\bNEXTCLOUD_USER\b", t).start()
Workflow: git show --stat on the rename commit (title said "rename …") showed only install.sh | 4 ++-- touched — grep then exposed boot-inject.sh:6 and nextcloud-bridge.py:10 still reading the bare name. That mismatch is what the canary tests now catch permanently.
Reproduced the exact trap in a git repo (`~/nc-installer`): 1. **Broken commit** (`feat(install): rename NEXTCLOUD_USER to NEXTCLOUD_ADMIN_USER`) — `git show --stat` showed only `install.sh` touched; runtime with `NEXTCLOUD_ADMIN_USER=alice`: ``` install:admin_user=alice ← writer auth-ok:admin ← bridge silently fell back ✗ boot-inject: admin user=admin ← boot-inject silently fell back ✗ ``` 2. **Fix applied** (3 code patches + 1 regression test, committed): ``` boot-inject.sh | 9 ++- install.sh | 7 +++ nextcloud-bridge.py | 11 +++- tests/test_install_env_restore.py | 64 ++++++++++++++++++--- ``` 3. **Grep closure** — every reader carries both names, ADMIN first: ``` grep -c NEXTCLOUD_ADMIN_USER → install.sh:3 nextcloud-bridge.py:3 boot-inject.sh:3 ``` 4. **Full pytest: 26 passed** (5 template units + 15 cross-reader consistency + 6 grep canaries). 5. **Canary negative test** — restored the broken-commit versions of `bridge.py`/`boot-inject.sh` (0 mentions of the new var): **8 tests failed**, catching exactly the missed-reader regression; reverted to the fix → 26 passed. **Edge cases covered:** both vars set (ADMIN wins), new var only, legacy only (backward compat), neither (default `admin`), empty-string vars treated as unset (bash `-n` vs Python truthiness agree), and cross-language consistency (bash readers vs Python reader vs template produce identical values per env).
{"model": "deepseek-v4-flash", "problem_class": "python-env-var-rename-consistency", "result": "passed", "tests": 26}