Preserve a live automation's active state on update - #7
Merged
jbedient-kizen merged 1 commit intoAug 17, 2026
Merged
Conversation
`AutomationDef.active` was `bool = False`, so an update spec that said
nothing about `active` was indistinguishable from one that explicitly
asked for `False` — every `automations update` from a spec omitting
the field silently deactivated a running automation, and `--dry-run`
didn't catch it because the preview printed the spec's value with no
reference to what was live.
Make `active` tri-state (`bool | None`, default `None`) and resolve
`None` in the planners, which already have the live state in hand:
`plan_create_automation` resolves an omission to `False`, matching
today's documented default; `plan_update_automation` resolves it to
whatever the live automation already is. The built payload still
always carries an explicit boolean. When a spec does explicitly flip a
live automation, the `--dry-run` preview now names it as a transition
("True → False (DEACTIVATES a live automation)") instead of a bare
value.
The plan's recommended approach was a `warnings: list[str]` channel on
PlanOperation, printed as its own line by _render_plan. Went with the
smaller-blast-radius option instead — embedding the transition string
in the existing preview cell — because PlanOperation is
`extra="forbid"` and plan JSON is a user-facing `--plan-file` artifact,
so a new required-shape field is a forward-compat break for anyone
applying a newer-CLI plan with an older CLI; the preview dict is
already `dict[str, Any]` and its only reader anywhere is
_render_plan's `f"{k}={v}"` join, so nothing else is affected. The
tradeoff: the transition string is quieter than a dedicated warning
line would be — at realistic terminal widths it sits mid-cell in a
wrapped row. Making it louder without the warnings channel isn't free
either: Rich markup in the string would leak into the JSON plan
artifact verbatim, and a generic " → " highlighter in the shared
renderer would flag ordinary field diffs across ten other planners
that already use that shape for non-warning transitions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
marshall-kizen
approved these changes
Aug 17, 2026
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.
Problem
AutomationDef.activedefaulted toFalse, so an update spec that saidnothing about
activewas indistinguishable from one that explicitly setit to
false. Combined with_build_automation_payloadwritingactiveunconditionally and
_merge_server_stateletting the payload win, everyautomations updatefrom a spec that didn't mentionactiveat allsilently deactivated a running automation — reported in First-Use Feedback
§7/§9 row #12.
--dry-rundidn't catch it either: the preview printed thespec's value with no reference to what was live, so
active=Falseread asa target state, not a change.
Solution
Made
activetri-state (bool | None, defaultNone) and resolved theNonecase in the planners, which already have live state in hand:plan_create_automationresolves an omission toFalse(unchangeddefault);
plan_update_automationresolves it to whatever the liveautomation currently is. The built PUT payload always carries an explicit
boolean either way. When a spec does explicitly flip a live automation,
the
--dry-runpreview now names it as a transition(
True → False (DEACTIVATES a live automation)) instead of a bare value.translate.py,set_active/_patch_field, and theactivate/deactivatecommands are untouched — they're the explicit path and were already
correct.
Testing
check.sh builder-cli --dir <worktree>, re-run fresh after the rebase ontomainat9aaf7e0: