# Codex --yolo: how to block git push --force

codex --yolo turns off the sandbox and the approvals together. How to get no prompts without that, a rule and a hook that block git push --force, and a one-minute test for whether they hold in your version.

`--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:

```toml
# ~/.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](https://learn.chatgpt.com/docs/agent-approvals-security)). 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:

```python
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:

```python
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](https://learn.chatgpt.com/docs/agent-configuration/rules)).

## 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`:

```js
#!/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:

```json
{
  "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](https://learn.chatgpt.com/docs/hooks)).

## 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:

```bash
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



## Related

- [How to stop Codex from deleting your files](/blog/stop-codex-from-deleting-files)
- [How to stop Claude Code from force-pushing](/blog/stop-claude-code-from-force-pushing)
- [Codex CLI hooks reference](/blog/codex-cli-hooks-reference)
- [Claude Code and Codex permission models compared](/blog/claude-code-vs-codex-permission-models)
