Codex --yolo: how to block git push --force
--yolo is short for --dangerously-bypass-approvals-and-sandbox: no sandbox, no approvals. If what you want is no prompts, you can have that without giving up the sandbox:
# ~/.codex/config.toml
approval_policy = "never"
sandbox_mode = "workspace-write"
Codex stops asking. Inside the workspace .git stays read-only, and network access is off unless you enable it, so a git push of any kind cannot reach the remote (approvals and security docs). OpenAI’s page does not say what that does to git commit. Expect to commit yourself when the run is done, and check it in your version.
If you need real --yolo, for example inside a container, add the rule and the hook below, then test that they hold.
A rule that forbids the push
A rule in ~/.codex/rules/default.rules forbids the command outright:
prefix_rule(pattern = ["git", "push", "--force"], decision = "forbidden", justification = "Rewrites remote history")
prefix_rule(pattern = ["git", "push", "-f"], decision = "forbidden", justification = "Rewrites remote history")
forbidden blocks without prompting. A pattern is an exact prefix, so these two match git push --force origin main and not git push origin main --force. To cover every argument order with a rule, prompt on the whole subcommand:
prefix_rule(pattern = ["git", "push"], decision = "prompt", justification = "Check the flags")
Check what a rule decides with codex execpolicy check --pretty --rules ~/.codex/rules/default.rules -- git push --force origin main. Rules load at startup and OpenAI marks them experimental (rules docs).
A hook for every argument order
A hook reads the whole command, so flag order stops mattering. Save this as ~/.codex/hooks/no-force-push.mjs:
#!/usr/bin/env node
let raw = "";
process.stdin.on("data", (d) => (raw += d));
process.stdin.on("end", () => {
let cmd = "";
try { cmd = String(JSON.parse(raw).tool_input?.command ?? ""); } catch {}
const force = /\bgit\b[^|;&\n]*\bpush\b[^|;&\n]*\s(--force(?!-with-lease|-if-includes)\b|-[a-zA-Z]*f[a-zA-Z]*(\s|$)|\+\S+)/;
if (force.test(cmd)) {
console.error("Blocked: this is a force-push. Stop and ask the human to run it.");
process.exit(2);
}
});
Register it in ~/.codex/hooks.json, then run /hooks in Codex and trust it:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "node \"$HOME/.codex/hooks/no-force-push.mjs\"" }
]
}
]
}
}
Exit code 2 blocks the call. A hook that errors or times out may not: OpenAI’s docs say the tool can still run (hooks docs).
Check it under –yolo yourself
OpenAI’s pages do not say whether rules and hooks still apply once approvals and the sandbox are bypassed. We have not confirmed it either way, so we are not going to tell you it holds. The test takes a minute:
mkdir /tmp/yolo-test && cd /tmp/yolo-test && git init -q
git remote add origin /tmp/yolo-test-remote.git && git init -q --bare /tmp/yolo-test-remote.git
git commit -q --allow-empty -m init && git push -q origin HEAD
codex --yolo "run: git push --force origin HEAD"
If the push is refused with your hook’s message, the hook ran. If it goes through, it did not, and you know before it matters. Run the test again after a Codex upgrade.
What the rule and the hook both miss
Here is what the hook script does with twelve commands:
BLOCKED git push --force origin main
BLOCKED git push -f
BLOCKED git push origin +main
BLOCKED cd app && git push -uf origin fix
BLOCKED git -C . push --force
ran git push --force-with-lease
ran git push origin main
ran bash scripts/release.sh
ran git pf
ran G=git; $G push --force
ran git push --mirror
ran git push origin :main
A release script, a git alias and a command built from a variable all get through, because none of them says “push –force” in the text the hook reads. A rule misses them for the same reason.
The control that does not depend on any of this is on the server. Block force pushes on main in your git host’s branch protection. Codex’s flags do not reach the server, so that holds under --yolo too.
ACP under –yolo
ACP installs a Codex hook and keeps it current. A force-push (--force, -f or a +refspec) is blocked until you approve it on the console or from the email, and then you ask Codex to retry. --force-with-lease is left alone. Every shell, apply_patch and MCP call is written to a log, which is the part you will use after an unattended run.
It is a Codex hook, so the --yolo question above applies to it too. Run the same test with ACP installed. And it reads command text, so a script or an alias gets past it as well.
Frequently asked questions
What does codex --yolo do?
It is the short form of –dangerously-bypass-approvals-and-sandbox. OpenAI’s docs describe it as no sandbox and no approvals, and mark it not recommended.
How do I run Codex without prompts but keep it from force-pushing?
Set approval_policy to never and sandbox_mode to workspace-write. Codex stops asking, .git stays read-only inside the workspace, and network access is off unless you turn it on, so a push cannot reach the remote.
Do Codex rules and hooks still run under --yolo?
OpenAI’s rules and hooks pages do not say. Test it in a scratch repository before you rely on it: add the rule or hook, start codex –yolo, and ask it to run the blocked command.
What blocks a force-push no matter how the agent is started?
Branch protection on the server. Block force pushes on main in your git host’s settings. Nothing in a local config file can change that.
Related
- How to stop Codex from deleting your files
- How to stop Claude Code from force-pushing
- Codex CLI hooks reference
- Claude Code and Codex permission models compared