Safe Automation Starts Before `--apply`

The dangerous command is rarely the one that says it will delete everything.
It is the tidy utility that loops across many repositories, opens a few pull requests, and promises to “save time.”
At one repository, a mistake is a correction. At twenty repositories, it is a campaign.
That is why the safety question for bulk automation is not “Does it have a dry-run flag?”
It is “What happens when someone runs it without thinking?”
If the answer is “it mutates remote state,” the tool has already chosen the risky default.
The default must be non-mutating
A useful bulk tool should make the first invocation a review artifact.
Without an explicit apply gate, it may calculate targets, show the patch it would make, and print the pull requests it would create. It must not clone-and-push its way into an organization’s history.
This polarity matters.
Adding --dry-run to a mutating default still leaves one very human failure mode: forgetting the flag.
Making dry-run the default removes that failure mode from the common path.
The two commands should mean visibly different things:
tool → inspect intended targets and changes
tool --apply → perform the already-reviewed external changes
Do not print a plan and then execute it in the same invocation. That is a performance of review, not review.
The dry run is part of the interface
For a one-off local script, dry-run output can be a convenience. For cross-repository work, it is the smallest usable specification of the change.
Reviewers need to see:
- the exact target set;
- files that are eligible or skipped;
- the mutation shape;
- exclusions and failure cases;
- whether a rerun would repeat existing work.
That output belongs in a pull request or a review record when behavior changes. Otherwise the script’s most important decision is only visible after it has been made.
The goal is not an elaborate planning subsystem.
It is one inspectable output that says, “This is what --apply will touch.”
Reruns need a stable answer
Bulk tasks often fail halfway through. A network request expires. A repository has an unexpected layout. Someone closes a laptop because the meeting was actually about something else. Very realistic fault injection.
The retry should not blindly apply the same patch again.
For a fixed and unambiguous payload, a small target-string marker can be enough: search for exactly what the script would insert, skip the file if it is already present, and report that skip. This gives the operation an important property:
run once + rerun after interruption = no duplicate patch
The pattern has limits. If the payload evolves, the old marker may no longer match. If someone hand-copies only the marker without the surrounding structure, a simple substring check can mistake a broken file for a completed one.
That is not a reason to start with a full parser and a framework of abstractions. It is a reason to state the ceiling: use the small marker while it remains unique and stable, then design an explicit migration when the payload changes.
Cheap work still needs an impact boundary
Skipping expensive CI for recognized bot-authored pull requests is a concrete example of why scope matters. The condition should be narrow: the relevant event, the known actor set, and the intended job boundary. Otherwise a cost-saving rule can silently suppress checks that branch protection or maintainers still need.
The same principle holds for every fleet tool. The mutation gate is not the whole safety story. The target predicate is a policy. The output is evidence. The rerun rule is recovery.
A small standard for large commands
Before letting a command change remote state across more than one repository, ask four questions.
- Is the default invocation unable to mutate anything?
- Can a reviewer inspect the exact target set before apply?
- Does a rerun avoid duplicating completed work?
- Is the policy narrow enough that skipped work stays understandable?
If any answer is no, the next line of automation is probably not the useful one.
Safe automation starts before --apply.
It starts with making the first command safe enough to run when nobody is at their sharpest.
Get the next post.
If you made it to the end, meet the next post in your inbox or RSS reader.