fix(scene): timed roller stop composes from current state, not the activation snapshot#456
Merged
Conversation
…tivation snapshot Platform audit follow-up (cover.py audit extended to light / switch / scene / button / binary_sensor / entity). Verdict: light, switch, button, binary_sensor and the shared entity base are sound — optimistic patterns correct (pre-mutation reads, post-await latch mutation, error reverts), tasks bounded and cancelled on removal, action buttons backgrounded with a discovery guard. One real defect found and fixed: scene.py _delayed_roller_stop fired 30-60 s after activation and replayed the FULL module snapshot captured at activation with only the scene's roller channels forced to STOPPED. Any channel of that module redirected during travel — another shutter adjusted by hand, a wall button, another scene — was silently overwritten with the stale state and restarted. The token guard only protects against re-activation of the same scene entity, not against everything else on the bus. Fix: compose the stop frame from the module's CURRENT coordinator state at timeout, and only force STOPPED on channels still doing what THIS activation sent — a channel someone has since stopped or reversed is not ours to touch any more (skip the write entirely when nothing commanded is still running). Tests: +4 (bystander channel preserved, redirected channel left alone, stale-token abort, missing-current fallback) — _delayed_roller_stop previously had zero coverage. 439 passed, ruff clean, mypy 100 (= baseline, no regression). https://claude.ai/code/session_01LH7yGDY8ttvZUVMVjfSNXj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Platform audit — the cover.py treatment extended to light, switch, scene, button, binary_sensor and the shared entity base (full line-by-line read, hunting the same bug classes: task self-cancellation, stale-state replay,
channel=Nonepayloads, AsyncMock-masked paths, optimistic races).✅ Clean (no changes needed)
_previous_brightnessread before mutation, error paths revert to coordinator state, the A/B latch mutates after the await (so a failed bus write never lies in the UI).discovery_runningguard.🟠 One real defect, fixed: stale-snapshot replay in the scene timed stop
_delayed_roller_stopfires 30–60 s after activation and replayed the full module snapshot captured at activation, with only the scene's roller channels forced to STOPPED. Any channel of that module redirected during travel — another shutter adjusted by hand, a wall button, another scene — was silently overwritten with the stale state and restarted. The token guard only protects against re-activation of the same scene entity.Fix: compose the stop frame from the module's current coordinator state at timeout, and only force STOPPED on channels still doing what this activation sent — a channel someone has since stopped or reversed is no longer ours to touch (the write is skipped entirely when nothing commanded is still running).
Tests
_delayed_roller_stoppreviously had zero coverage. +4 regressions: bystander channel preserved, redirected channel left alone, stale-token abort, missing-current fallback.439 passed,ruffclean,mypy100 (= baseline, no regression).https://claude.ai/code/session_01LH7yGDY8ttvZUVMVjfSNXj
Generated by Claude Code