| name | safety-rails |
| description | Use whenever you're about to run a destructive Bash command, push code, or spend Codex tokens — explains every cap and denylist this plugin enforces and how to extend them. |
| when_to_use | About to run `git push --force` / `npm publish` / `terraform apply` / `rm -rf` / SQL drops | budget exhausted | hitting iteration cap |
| allowed-tools | Bash(autopilot-codex:*), Read |
AutoPilot Codex — safety rails
The autopilot moves faster than a human reviewer. The safety rails are
intentionally narrow — Claude Code's own permission system is the
primary line of defence; this is the backstop for the loop.
Bash denylist (hooks/safety-rails.sh)
PreToolUse:Bash hook denies these patterns with
hookSpecificOutput.permissionDecision="deny":
| Pattern | Matched as | Reason |
|---|
rm -rf / (and any absolute root) | regex on /[^[:space:]]* after rm -rf | irreversible |
rm -rf .git | (...)*\.git after rm -rf | drops history |
git push --force / git push -f | git push.*--force|-f | rewrites shared branches |
git reset --hard | literal | discards uncommitted work |
git clean -f* | git clean -[a-zA-Z]*f | deletes untracked |
DROP TABLE / DROP DATABASE / TRUNCATE TABLE | case-insensitive SQL | irreversible |
terraform destroy | literal | tears down infra |
mkfs.* | mkfs(\.[a-z0-9]+)? | wipes filesystems |
shutdown / halt / reboot | literal | takes the host down |
:(){ :|: & };: | fork-bomb regex | DoS |
npm publish (without --dry-run) | requires --dry-run to allow | unintended release |
What's NOT blocked (by design)
git push origin main (no --force) — recoverable, allowed.
rm -rf /tmp/<project>/<dir> — /tmp paths are not
special-cased in the regex; we strip quoted bodies before matching to
avoid false positives in commit messages.
- Anything inside
"..." or '...' quoted strings — stripped to a
QUOTED placeholder before the deny regex runs (v0.1.3 fix).
Budget cap (scripts/budget-guard.mjs)
PostToolUse and Stop hooks call node budget-guard.mjs check after the
status check. Behaviour:
- Reads
state.budget_used_usd and state.budget_usd.
- If
used >= cap and status == "running", flips status to
stopped_budget and exits 2.
- Hooks see exit 2 and short-circuit: no Codex review, no Codex verdict,
emit a non-blocking
additionalContext note.
Defaults: budget_usd = 5.00. Override with AUTOPILOT_BUDGET_USD env
or /autopilot ... --budget 12.50.
This is a soft cap on already-charged turns. A single very large
Codex turn can put budget_used_usd above budget_usd in one step;
the next hook short-circuits, but the over-spend that turn is real.
Iteration cap
cmdVerdict increments state.iteration per call. When
iteration >= max_iterations (default 30), status flips to
stopped_iteration_limit. Stop hook lets Stop proceed.
No-progress runaway guard
cmdVerdict tracks no_progress_streak. When Codex returns 'continue'
3× in a row without commits or working-tree changes (excluding
docs/autopilot/ artefacts), status flips to aborted with a
forced_abort reason.
Side-effect operations require explicit user approval
The autopilot loop never runs git push, npm publish,
terraform apply, or anything else that talks to the outside world.
The implementer is expected to ask the user. If a user-facing message
mentions these, surface them through /autopilot-codex:codex-status
and let the user opt in.
Extending the rails
To add a Bash denial pattern:
- Edit
hooks/safety-rails.sh. Add a grep -qiE line below the
existing ones, model it on the SQL block.
- Add positive + negative regression tests to
scripts/__tests__/hooks.test.mjs. The negative case (a benign
variant that should pass) is just as important as the deny case.
- Test locally: pipe a fake stdin into the hook —
echo '{"tool_name":"Bash","tool_input":{"command":"…"}}' | bash hooks/safety-rails.sh.
To raise / lower the budget: set AUTOPILOT_BUDGET_USD in your env or
pass --budget USD to /autopilot. The hook will pick it up on the
next session-start (or via budget-guard init).
To raise / lower the iteration cap: set AUTOPILOT_MAX_ITERATIONS=50
before invoking /autopilot. There's no per-session override yet.