Automation & Agents

Confirmation Gates for Irreversible Actions: Use an Explicit Flag, Not an Interactive Prompt

Your safety check will fail someday. What you should design is which way it falls when it does. Make doing nothing the default, make execution require an explicit flag the user typed, and never let the program guess whether someone is at the keyboard.

10/14/2026 | 4 min read | About 8 minutes to read, with a Python skeleton you can copy

The one idea that matters

Your safety check will fail someday. What you should design is not “will it fail” but “which way does it fall when it does.”

Most people write a confirmation gate by asking “how do I make sure someone looked at this?” That leads you to detect the environment: is there a terminal, is it interactive mode, is it CI. And environment signals change with the operating system, the shell, and how the program was launched.

The right approach: make “do nothing” the default, and make “execute” require a deliberate action by the user that cannot happen by accident.

Four checks

Check 1: Use an explicit flag, not an interactive prompt

Approach What it depends on Risk
Program asks “Are you sure? (y/N)” “Someone is at the keyboard right now” When scheduled or called from a script, standard input may be a file or a pipe, and it can read a ready-made y and carry on
Require --send --yes “The user actually typed that string” If it is missing, refuse. No asking, no waiting, no guessing

A prompt depends on “is anyone in the environment,” and the environment will lie to you. A flag depends on “did the user actually type it,” and that cannot be faked.

Check 2: The default must be “do nothing”

With no arguments, the program should run in dry-run mode: print everything that would happen, but call no external API and overwrite no file.

The test: when a beginner mashes your command for the first time, the worst outcome should be “nothing happened,” not “it went out.”

Check 3: When the gate fails, fall toward “refuse”

Imagine your own condition is broken: it returns the opposite value, throws, or reads an empty variable. The program should land on “refuse,” not “proceed.”

  • Falls toward refuse: if not explicitly_authorized: refuse. Any unexpected state counts as “not authorized”
  • Falls toward allow: if detected_no_authorization: refuse. When the check itself breaks, the refusal is skipped

Check 4: Batches of irreversible actions must allow partial success

If you do ten irreversible things at once (say, a ten-post thread) and the fifth fails, the first four have already happened and cannot be taken back. The most dangerous design is automatically retrying the whole batch, which gives you duplicates.

The right way: report how far it got, then stop and let a person decide whether to continue, roll back, or leave it.

Step by step

Step 1: Find every irreversible action in your system

Ask one question: after this action runs, can I fully undo it within 10 seconds, leaving no trace? If not, it is irreversible. Common ones: publishing publicly, deleting files or rows, payments and transfers, sending email, overwriting existing files, pushing to a public repository.

Step 2: Wrap each irreversible action in a three-part structure

def main(args):
    # Part 1: dry run by default. No flags = only print what would happen
    dry_run = not args.send

    # Part 2: explicit authorization gate. Without the full flag, exit. Do not ask or wait
    if args.send and not args.yes:
        print("--send must be paired with --yes to run. This action is irreversible.")
        return 2          # non-zero exit code so the caller knows nothing was done

    # Part 3: actually run, reporting each item's result
    results = []
    for item in items:
        result = do_one_thing(item, dry_run=dry_run)
        results.append(result)
        if not result.ok:
            # Do not auto-retry the whole batch: what already finished cannot be recalled
            print(f"Failed after {len(results)-1} items. Stopping. A person must decide what happens next.")
            break
    return 0 if all(r.ok for r in results) else 1

Step 3: Make “what will happen” concrete

The dry-run output should let a person decide whether to proceed just by reading it. Do not print only “will publish 3 posts.” Print the actual content that would be sent, the destination, and the cost. If the action spends money (calls a paid API), compute the estimated amount during the dry run.

Step 4: Add a platform-independent cap

When money or quota is involved, do not rely only on the limit in the provider’s dashboard. Keep a running total and a cap in your own program, and refuse once it is exceeded. Two gates beat one.

Step 5: Actually test the “nobody is there” case

This is the step most people skip. Launch your command from a scheduler, a pipe, or another script and check that it behaves as you expect with no one present. A quick test is to redirect standard input to empty and see whether the program crashes, hangs, or just proceeds. All three outcomes fail. A pass is “refuse clearly and say why.”

Traps you will hit

Trap 1: Using “is this an interactive environment?” as the gate

Every language has a function that checks whether standard input is attached to a terminal. It works fine in your own terminal, but under a different shell, a different OS, or when another program calls yours, it can return the opposite answer. Do not use it to decide whether to run an irreversible action. Using it to decide whether to print colored text is fine, since a wrong guess hurts no one.

Trap 2: Auto-retrying the whole batch after a failure

Retrying is a good habit for queries and a disaster for irreversible actions. You get duplicate posts, duplicate transfers, duplicate emails. The test: if I run this again, will the result become two copies? If yes, no automatic retry.

Trap 3: A report that lies

After the run it prints “done,” but everything actually failed, because that line was hard-coded. This is especially dangerous for irreversible actions, since you will believe it already happened and stop checking. Derive the status headline from the real results, and distinguish all succeeded, partly succeeded, and all failed.

Universal rescue: how to ask an AI when you are stuck

Copy the block below to any AI assistant and replace the angle brackets with your situation:

I am writing a feature with irreversible consequences: <describe the action, e.g. "publish a post to social platforms">.
My language is <language>, and it runs as <command-line tool / scheduler / web backend>.

Please review my confirmation mechanism and answer these four points specifically:
1. Is my gate based on "detecting the runtime environment" or on "the user's explicit statement"?
2. When no arguments are given, what happens by default? Does it do nothing at all?
3. If my own condition is wrong or throws an exception, does the program refuse or proceed?
4. If this operation is run twice, will it produce two results? If so, is my failure handling auto-retrying?

Go one step at a time and give me the revised code directly.
I am a beginner, so explain the reason for each change.

If you are not even sure whether your program counts as irreversible, ask this first:

For the code below, list every operation that "cannot be fully undone within 10 seconds,"
sorted by risk. <paste your code>

One-line summary

For irreversible actions, gate on a flag the user explicitly typed, not on the program guessing its environment; make the default do nothing, and make a failed gate fall toward refusal.

Support this work:Support on Ko-fi