Skip to content

Commit 39e0a95

Browse files
kingpanther13claude
andcommitted
fix(flows): keep the stored value when a revisited step's write is spent
claim_write allows one reused write per (step, path). Once spent, an optional edit-mode field fell back to omission — which hands voluptuous the field's STATIC default and overwrites the entry's stored value, the exact wipe this mode exists to stop. A menu loop revisiting the same step is enough to reach it. The step's own value now goes back instead. The required-field branch still omits: there, omission raises HA's own loud 'required key not provided' rather than losing data silently. Found by CodeRabbit review on #2256. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012d29UJTiH4Uy2Pm37SBPtr
1 parent 39691a1 commit 39e0a95

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

src/ha_mcp/tools/config_entry_flow_form.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,15 +441,23 @@ def _edit_mode_submission(
441441
the caller asked to write (Patch76 review, issue #2254).
442442
443443
Only a field the caller never named anywhere falls through to the step's
444-
own value.
444+
own value — as does one whose reused write is already spent, rather than
445+
letting an omission apply a static default over what is stored.
445446
"""
446447
recorded = reuse_state.recorded_value(path_prefix, name)
447448
if recorded is _MISSING_DEFAULT:
448449
return _current_value_backfill(field)
449450
if recorded is None:
450451
return _NO_SUBMISSION
451452
if not reuse_state.claim_write(dotted):
452-
return _NO_SUBMISSION
453+
# The one reused write per (step, path) is spent — a menu loop is
454+
# revisiting this step. Falling back to omission would let voluptuous
455+
# substitute the field's STATIC default over the entry's stored value,
456+
# which is the very wipe this mode exists to stop, so send what the
457+
# step presented instead (CodeRabbit review, issue #2254). The
458+
# required-field branch still omits: there, omission raises HA's own
459+
# loud "required key not provided" rather than losing data silently.
460+
return _current_value_backfill(field)
453461
return recorded, True
454462

455463

tests/src/unit/test_flow_options_preserve.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,54 @@ def test_a_callers_value_outranks_a_later_steps_suggestion(self) -> None:
607607
f"The later step overwrote the caller's value: {second}"
608608
)
609609

610+
def test_a_revisited_step_keeps_the_stored_value_after_the_write_is_spent(
611+
self,
612+
) -> None:
613+
"""A menu loop must not let the static default win on encounter three.
614+
615+
``claim_write`` allows one reused write per (step, path). Once spent,
616+
omitting the key handed voluptuous the field's STATIC default, which
617+
overwrote the entry's stored value — the exact wipe this mode exists
618+
to stop (CodeRabbit review, #2256). The step's own value goes back
619+
instead.
620+
"""
621+
step: dict[str, Any] = {
622+
"type": "form",
623+
"step_id": "init",
624+
"data_schema": [
625+
{
626+
"name": "workdays",
627+
"required": False,
628+
"optional": True,
629+
"default": ["mon", "tue", "wed", "thu", "fri"],
630+
"description": {"suggested_value": ["mon", "wed", "fri"]},
631+
},
632+
],
633+
}
634+
reuse_state = _ReuseState()
635+
remaining: dict[str, Any] = {"workdays": ["sat"]}
636+
payloads = [
637+
_handle_form_step(
638+
"flow-2254",
639+
dict(step),
640+
remaining,
641+
None,
642+
set(),
643+
reuse_state,
644+
keep_current_values=True,
645+
)
646+
for _ in range(3)
647+
]
648+
649+
# The caller's value applies while the budget lasts...
650+
assert payloads[0]["workdays"] == ["sat"]
651+
assert payloads[1]["workdays"] == ["sat"]
652+
# ...and once spent the STORED value goes back, never the static
653+
# default that an omission would have substituted.
654+
assert payloads[2]["workdays"] == ["mon", "wed", "fri"], (
655+
f"Spent write fell back to the schema default: {payloads[2]}"
656+
)
657+
610658
def test_create_flow_still_drops_a_redeclared_optional_field(self) -> None:
611659
"""Flag off keeps the pre-#2254 shape: nothing goes back at all."""
612660
reuse_state = _ReuseState()

0 commit comments

Comments
 (0)