Cookiecutter #2219: When One Bad Override Silently Kills the Rest
A 7-line fix in cookiecutter/generate.py stops apply_overwrites_to_context from bailing out on the first invalid entry, preventing silent config merge corruption.
The Bug
Cookiecutter has a configuration merging function, apply_overwrites_to_context, that applies user-provided overrides (from default_context or extra_context) onto a template’s cookiecutter.json defaults [1].
The problem [2]: when the first override entry is invalid (wrong choice list, unparseable boolean), the function immediately raises ValueError and exits the loop — leaving all subsequent overrides never processed.
For extra_context, this surfaces as a hard ValueError, which is fine. But for default_context, the error is caught and downgraded to a warning by generate_context(). The result: silent partial config merge. Only the entries before the first invalid one get applied. Everything after is silently dropped with no way for the template author to detect it.
Root Cause
The entire apply_overwrites_to_context function is structured as a single for variable, overwrite in overwrite_context.items() loop with three raise ValueError(...) exit points:
- Multi-choice validation (line 89):
set(overwrite).issubset(set(context_value))fails - Choice validation (line 103): overwrite not in the valid choices list
- Boolean conversion (line 120): YesNoPrompt cannot parse the string
Any one of these kills the entire loop.
The Fix
Instead of raising immediately on the first error, collect all errors into a list, continue processing the remaining overrides, then raise a single composite ValueError at the end if any errors were collected.
# Before (3 raise points in the loop body)
raise ValueError(msg)
# After
errors.append(msg)
# At end of function
if errors:
raise ValueError('; '.join(errors))
The diff is clean — 6 lines added, 3 lines removed:
+ errors: list[str] = []
for variable, overwrite in overwrite_context.items():
...
- raise ValueError(msg)
+ errors.append(msg)
...
- raise ValueError(msg)
+ errors.append(msg)
...
- raise ValueError(msg) from err
+ errors.append(msg)
...
+
+ if errors:
+ raise ValueError('; '.join(errors))
Now when a user’s ~/.cookiecutterrc has multiple invalid overrides, every invalid entry is reported in the warning:
Invalid default received: invalid_value provided for choice variable
framework, but the choices are ['fastapi', 'flask', 'django'].;
invalid provided for variable debug_mode could not be converted to a boolean.
Previously, only the first error would surface. The remaining overrides were silently lost.
Key Design Decision
This fix modifies apply_overwrites_to_context itself rather than wrapping individual calls in try/except at the call site. That’s intentional:
- Works for both callers:
default_context(which wraps in try/except → warning) andextra_context(which lets the error propagate as a hard failure) - No caller changes needed: The existing
generate_context()try/except still catches the composite ValueError and emits a single consolidated warning - Preserves error behavior: All existing tests that expect
pytest.raises(ValueError)still pass because the composite error is raised at function exit
Lessons
- Error collection over fail-fast: When processing a batch of independent items, collect all errors before stopping. The user needs the full picture, not just the first failure.
- Fix the function, not the call site: A proper fix belongs in the function that has the bug, not in every place it’s called.
- Tests prove correctness: All 32 tests in
test_generate_context.pypass without modification, including the test that checkspytest.warns(UserWarning, match="Invalid default received").
[1] Cookiecutter source: cookiecutter/generate.py, function apply_overwrites_to_context — https://github.com/cookiecutter/cookiecutter/blob/main/cookiecutter/generate.py
[2] GitHub issue #2219 — “apply_overwrites_to_context silently drops remaining overrides after first invalid entry” — https://github.com/cookiecutter/cookiecutter/issues/2219