Pakkit.net
← Back to blog

Automation

Restart Only When Something Actually Changed

An automation run that bounces the service every single time — even when it changed nothing — isn't idempotent, it's just quietly disruptive, so gate the side effects behind real change detection and separate making a change from applying it.

  • Automation
  • Operations
  • Reliability
  • Configuration

I was reviewing a config-management playbook that ended, every run, by restarting the service it managed. Unconditionally. Run it to change a setting: it restarts, fine. Run it again to confirm nothing drifted: it also restarts, bouncing a live service to apply a change it didn’t make. “Re-run it to be sure” wasn’t free — it was a service interruption every time. The automation looked idempotent because the config ended up the same, but its side effects weren’t idempotent at all, and that gap is where a lot of automation quietly does harm.

Idempotent data, non-idempotent side effects

We usually judge automation by its end state: run it twice, the file has the same contents, so it’s “idempotent.” But applying a change and acting on that change are two different things, and the action is where the disruption lives. A restart, a reload, a reboot, a cache flush, a notification, a downstream API call — those have consequences whether or not the underlying config actually moved. An automation that fires them on every run, regardless of change, is disruptive by default.

Converging the config is only half of idempotence. If the run bounces a live service even when nothing changed, the behavior isn’t idempotent, no matter what the file looks like.

The tell is exactly the one I hit: “just run it again to be safe” causes a visible effect. If re-running your automation isn’t a no-op in practice — if it restarts things, pages people, or churns state — then it’s not safe to run again, and “safe to run again” is the whole promise idempotence is supposed to make.

Gate the side effect behind real change detection

The fix is to make the disruptive action conditional on an actual change. Configuration tools have this built in — the handler pattern: a task announces “I changed something,” and the restart only runs if some task actually made a change this run. Nothing changed? No notification, no restart, a clean no-op. Something did? The restart fires exactly once, at the end, after all the changes that need it. The service gets bounced when it needs bouncing and left alone when it doesn’t.

The general principle, tool-agnostic: detect change, then act on change. Don’t restart because the playbook ran; restart because the config the service reads is different than it was. That single conditional turns “every run is a service bounce” into “only meaningful runs touch the service,” which is what makes the automation safe to run on a schedule, in a loop, or nervously three times in a row.

Separate making the change from applying it

There’s a second, related move that gives you even more control: split “write the new configuration” from “make the service pick it up.” They’re different operations with different risk profiles. Staging a config change is cheap and reversible; bouncing a live service is disruptive and timing-sensitive. When they’re welded together, you can’t do one without the other — you can’t pre-stage a change during the day and apply it in a maintenance window, because writing it is applying it.

So I like automation that lets me stage the change now and flip the “apply it” switch separately — write the new config, but only restart when I explicitly say so. That separation is what lets a change be prepared safely and activated deliberately, instead of every edit being an immediate, unschedulable service interruption. It’s the same instinct as a dry run: give yourself a step between “decided” and “done.”

Re-running should be boring

The test I apply to any automation now: what happens on the second identical run? If the answer is “nothing — it detects no change and does nothing,” it’s built right. If the answer is “it restarts the service again,” the side effects need a change gate. Boring re-runs are the goal, because boring re-runs are what make automation trustworthy enough to run constantly — the property behind everything from config management to verifying a change actually took.

None of this is complicated; it’s mostly a discipline of asking “does this action need to happen, or did I just do it because the script reached the bottom?” Gate the disruptive parts on real change, split staging from applying, and your automation stops being something people are afraid to run twice. If you’ve untangled a playbook that bounced things it didn’t need to, I’d like to hear about it.