Skip to content

Check a command before an agent runs it

An agent in a loop runs whatever shell command it decides on. Here you put decide in front of it, so a command that could do lasting harm waits for a person.

You'll end with

checked.sh in your project. It runs a command only when command-risk flags nothing, so an agent or a script can call it in place of sh -c.

! severe yes
You need
  1. command-risk asks four yes-or-no questions about each line: could the command destroy work, leak a secret, publish or deploy something, or cause severe harm beyond your checkout? Try it on two commands that delete things:

    ~
    printf '%s\n' 'git reset --hard HEAD~3' 'rm -rf ~/' | decide run command-risk
    Output as text
    $ printf '%s\n' 'git reset --hard HEAD~3' 'rm -rf ~/' | decide run command-risk
    Running command-risk on 2 lines · typesafe jev-latest
    
    stdin:1  git reset --hard HEAD~3
    ! destructive  yes     97%
      leak         no      95%
      publish      no      96%
      severe       no      96%
    
    stdin:2  rm -rf ~/
    ! destructive  yes     98%
      leak         no      89%
      publish      no      73%
    ! severe       yes     98%
    
    ✓ 2 answered  ! 2 flagged  200ms
    Flagged: stdin:1, stdin:2
    See these results again with: decide runs view 20261008-045200-9a5d

    Both are flagged as destructive. Only rm -rf ~/ is severe. A hard reset loses local work, but it stays inside your checkout. Your percentages may differ a little.

    decide only reads the text. It never runs the commands it judges.

  2. Save this as checked.sh and make it executable with chmod +x checked.sh:

    checked.sh
    #!/bin/sh
    # Usage: ./checked.sh 'git reset --hard HEAD~3'
    printf '%s\n' "$1" | decide run command-risk --fail-on flagged
    case $? in
    0) sh -c "$1" ;;
    2) echo "decide flagged this command; not running it" >&2; exit 2 ;;
    *) echo "decide could not check this command; not running it" >&2; exit 1 ;;
    esac

    --fail-on flagged makes decide exit with code 2 when an answer is flagged. The script runs the command on 0, refuses it on 2, and refuses it on 1 too, which means decide could not answer, such as when the key is wrong. To run the command anyway in that case, change the last line to run it.

  3. Run a harmless command, then a force push, and print the exit code:

    ~
    ./checked.sh 'echo it ran'
    ./checked.sh 'git push --force origin main'
    echo $?
    Output as text
    $ ./checked.sh 'echo it ran'
    Running command-risk on 1 line · typesafe jev-latest
    
    stdin:1  echo it ran
      destructive  no      100%
      leak         no      98%
      publish      no      98%
      severe       no      100%
    
    ✓ 1 answered  nothing flagged  200ms
    See these results again with: decide runs view 20261008-045224-adb4
    it ran
    
    $ ./checked.sh 'git push --force origin main'
    Running command-risk on 1 line · typesafe jev-latest
    
    stdin:1  git push --force origin main
    ! destructive  yes     95%
      leak         no      61%
      publish      no      78%
    ! severe       yes     91%
    
    ✓ 1 answered  ! 1 flagged  200ms
    See these results again with: decide runs view 20261008-045231-a636
    Exiting with code 2 because 1 item was flagged (--fail-on flagged)
    decide flagged this command; not running it
    $ echo $?
    2

    The force push is refused, with exit code 2. It is flagged on destructive and severe, so the push never starts. The harmless echo, just before it, ran.

    To flag only the worst commands, read severe alone with --json, as the Claude Code plugin does. A deploy is flagged on publish, so checked.sh refuses it too.

decide is open source under Apache 2.0.Made by Deep Noodle