# Codex setup

Source: https://pushary.com/docs/agents/guides/codex
Last updated: 2026-11-07

> Set up Codex notifications and phone approvals in one command. Native Codex hooks gate shell commands, apply_patch edits and MCP calls before they run.

Pushary connects Codex to your phone through native Codex hooks (small programs Codex runs around each tool call) and the Pushary MCP server. Codex shell commands, `apply_patch` edits and MCP calls go through your Pushary policy, and the ones that need a person wait for your answer. It works with the Codex CLI and with the Codex inside the ChatGPT desktop app.

## Set it up [#set-it-up]

```bash
npx pushary@latest setup
```

On Windows PowerShell, run `npx.cmd pushary@latest setup`. The CLI needs Node.js 20.17+, 22.13+ or 23.5+.

<Steps>
  <Step>
    ### Scan the QR code with your phone [#scan-the-qr-code-with-your-phone]

    Setup shows a QR code first. Scan it with your phone's camera or the Pushary app. Sign in or create an account, check that the code on your phone matches the one in your terminal, then tap **Approve** on &#x2A;*Connect this computer?** and allow notifications.

    New accounts start a 3-day free trial, then pay $9.99 a month. A card is needed to start the trial. If this computer already has a Pushary key, setup reuses it and skips this step.
  </Step>

  <Step>
    ### Pick Codex [#pick-codex]

    Setup asks &#x2A;*Which agents do you use?** with the agents it found already selected. Keep Codex selected. To skip the question, run `npx pushary@latest setup --agents codex`.
  </Step>

  <Step>
    ### Trust the hooks, if setup asks [#trust-the-hooks-if-setup-asks]

    Codex runs a hook only after you trust it. Setup writes the trust entries for you and asks Codex to confirm them, so there is usually nothing to do.

    If setup prints **One step left, and it happens inside Codex**, open Codex, type `/hooks` and trust the Pushary hook commands. In the ChatGPT desktop app, open Settings, then **Hooks**. Until you do, Codex runs without phone approvals. Pushary updates keep the same trust, so Codex does not ask again.
  </Step>

  <Step>
    ### Answer the test question [#answer-the-test-question]

    Setup sends a test push, then a test question. Answer it on your phone. The terminal says **You're set** once your answer arrives.

    The test question proves your phone gets questions. It does not prove Codex waits for you. If this Codex is too old for hooks, or setup could not read its version, setup says **Codex approvals are not on yet** instead. If Codex does not run every Pushary hook yet, for example because it has not trusted them or one is turned off, setup says **Codex is not running every Pushary hook yet**. Either way it tells you what to do.
  </Step>

  <Step>
    ### Restart Codex [#restart-codex]

    Codex reads hooks when a session starts. Start a new session.
  </Step>
</Steps>

<Callout type="info">
  Native hooks need Codex 0.122 or newer. On an older Codex, setup installs the legacy notify handler instead, which sends notifications only. Upgrade Codex and run setup again to get approvals.
</Callout>

On a Mac you can use [Pushary Isle](/docs/agents/mac-app) instead. It connects Codex for you and needs no Node.

## When the Codex app asks to use an app on your Mac [#when-the-codex-app-asks-to-use-an-app-on-your-mac]

When Codex wants to control an app on your Mac, the Codex app shows a card such as "Allow Computer Use to use Calculator?". Hooks never see that card. An experimental switch sends it to Pushary as well.

With the Pushary Mac app, version 0.1.101 or later, the switch is in Settings, under Agents: "Codex app", "Ask me when Codex wants to use an app or its browser" (before 0.1.103 it reads "Ask me when Codex wants to use an app"). It is on unless you turn it off. The app turns it on once you are signed in on a paid plan and it finds the Codex app, and repairs it after a Codex update. Quit Codex and open it again after it changes.

The Mac app's login item runs Pushary's own program, so macOS treats it as part of Pushary. If macOS does not start it when you log in, for example because it is turned off under Login Items in System Settings, Pushary leaves it that way and the row in Settings says so.

Without the Mac app, turn it on once from a terminal:

```bash
pushary codex --app-cards on
```

Then quit Codex and open it again. From then on the Codex app starts its engine through a small Pushary pass-through every time you open it, including after a restart. `pushary codex --app-cards status` says whether it is on, and `pushary codex --app-cards off` turns it off. It uses the CLI's own account, so run `pushary setup` first. The switch in the Mac app and the command are the same switch: either one shows what the other did, and the Mac app does not turn back on what you turned off with the command.

To try it for one launch without changing anything that lasts, quit Codex and run `pushary codex --desktop`. Keep that terminal open; closing Codex ends it.

You get one question: "Allow Codex to use Calculator for this conversation?" Yes allows that app for that conversation only, and No denies it. If you answer the card in Codex first, the Pushary question is withdrawn. "Always allow" is only ever offered in Codex. The question follows your delivery mode like any other: in Terminal mode nothing is sent and the card waits in Codex. Pushary waits ten minutes for an answer, then withdraws the question and leaves the card in Codex.

From CLI 1.9.59, the same switch also carries three questions from Codex's browser: access to a site, a download and an upload. Each names the site. A download or upload question also carries the address up to the end of its path. Nothing after a `?` or `#` is in that question, and the address ends in … when something was left out. The path itself is sent, so a link that keeps a token in its path sends it. Codex shows the whole address. For a download or an upload, Yes allows that one transfer. For a site, Yes allows it for the rest of the conversation, or for the turn if Codex is set to ask each turn, and No blocks it for the conversation, as Codex's own buttons do. "Always allow" and "Allow for all sites" stay in Codex, and so do the other Browser use questions, such as Developer Tools access, verification codes and sign-in. In the Mac app this needs version 0.1.103 or later.

Pushary only asks when the card comes from Codex's own Computer Use or its own browser and says exactly what Codex shows. A card from any other tool, or one worded differently, stays in Codex.

Commands, file edits and Codex's own questions are not touched by this launch. The hooks above keep handling them. If Pushary cannot be reached or this computer is signed out, the card stays in Codex.

### What the switch changes on your computer [#what-the-switch-changes-on-your-computer]

| What                                                  | Where                                                      |
| ----------------------------------------------------- | ---------------------------------------------------------- |
| A launcher script                                     | `~/.pushary/codex-app/codex`                               |
| A login item that applies the setting after a restart | `~/Library/LaunchAgents/com.pushary.codex-app-cards.plist` |
| The setting itself, for your login session            | `CODEX_CLI_PATH`, set with `launchctl setenv`              |

`pushary codex --app-cards off` removes the login item and the setting, and replaces the launcher with one that only runs Codex's engine. The file stays on purpose: while the switch is on, the Codex app records the launcher's path in its own settings (`~/.codex/config.toml`) each time it starts, and a Codex that is still open keeps starting its engine through it. After `off`, nothing of Pushary sits in between. `pushary clean` removes the launcher as well, so quit Codex and open it again after a clean. If another program already set `CODEX_CLI_PATH`, Pushary leaves it alone and says so.

The launcher is built to step aside. It hands every engine command except the app's own protocol straight to Codex's engine. For that one protocol it starts Pushary and checks that Pushary started the engine; if Pushary is missing or broken, or the launcher cannot write beside itself, the launcher starts the engine itself and Codex runs without the cards. If the launcher is stopped, Pushary stops the engine within a few seconds, as Codex expects. If an update moves the engine, the launcher turns its own setting off and says so; open Codex again and it starts normally. `pushary codex --app-cards status` says when the switch is on but Pushary or the engine is no longer where the launcher expects.

Running `pushary setup` again while the switch is on is safe: setup looks through the launcher to Codex's own engine.

This is experimental and not enabled by ordinary setup. It relies on a launch setting of the Codex app that OpenAI does not document, and was checked against Codex app 26.1002 (engine 0.162).

## What setup changes on your computer [#what-setup-changes-on-your-computer]

| File                                            | What changes                                                                                                                                                                                                                                                  |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `~/.codex/config.toml`                          | Adds `[mcp_servers.pushary]` with your key in an `Authorization` header and `default_tools_approval_mode = "approve"`, so Pushary's own tools run without a prompt. Adds trust entries for the Pushary hooks. Removes the old `pushary-codex` notify handler. |
| `~/.codex/hooks.json`                           | Adds 11 hook events. Every one runs the same command, `pushary-codex-hook`, so there is one command to review.                                                                                                                                                |
| `~/.codex/AGENTS.md`                            | Adds a Pushary block that tells Codex when to ask through Pushary and when to ask in the terminal.                                                                                                                                                            |
| `~/.codex/skills/pushary/SKILL.md`              | The Pushary skill.                                                                                                                                                                                                                                            |
| `~/.pushary/config.json` and your shell profile | Your API key, as `PUSHARY_API_KEY`.                                                                                                                                                                                                                           |
| `AGENTS.md` in the current folder               | Only if you say yes when asked.                                                                                                                                                                                                                               |

If you set `CODEX_HOME`, these files go there instead of `~/.codex`. The key sits in `config.toml` itself, so Codex reaches the MCP server even when it starts from a shell that never loaded your profile.

### What each hook does [#what-each-hook-does]

| Event                                                        | What happens                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PermissionRequest`                                          | Runs when Codex is about to ask you in the terminal: a sandbox escalation, a forced `rm`, an MCP tool not marked read-only, or a patch outside the writable folders. Your approval mode decides whether the question goes to your phone. Yes runs it, No denies it. If your rule says Ask in terminal on timeout, it falls back to Codex's own prompt. |
| `PreToolUse`                                                 | The enforcement backstop. Runs before every shell command, `apply_patch` call, MCP call and Codex question. The kill switch and always-deny rules block the call here, even when Codex would not have asked. See below for when it asks you.                                                                                                           |
| `PostToolUse`                                                | Records `tool_complete` or `tool_error` and clears pending phone questions.                                                                                                                                                                                                                                                                            |
| `UserPromptSubmit`                                           | Saves your prompt as the session's task title. Set `PUSHARY_TASK_TITLES=off` to turn it off.                                                                                                                                                                                                                                                           |
| `Stop`                                                       | Marks the session idle and cancels pending phone questions.                                                                                                                                                                                                                                                                                            |
| `SessionStart`, `SessionEnd`                                 | Show the session in your session list from its first second to its end.                                                                                                                                                                                                                                                                                |
| `SubagentStart`, `SubagentStop`, `PreCompact`, `PostCompact` | Record subagents and context compaction. They decide nothing.                                                                                                                                                                                                                                                                                          |

Codex stops a hook after 180 seconds, so Pushary waits for your answer for at most about 170. By default it waits for your rule's timeout, 60 seconds. When nobody answers, a `PermissionRequest` goes back to Codex's own prompt unless an Always rule auto-approves or auto-denies, and a call `PreToolUse` asked you about is refused unless an Always rule approves on timeout. Either rule decides only when its timeout is at most about 170 seconds.

### When PreToolUse asks you [#when-pretooluse-asks-you]

`PreToolUse` fires on every call, so it asks only about the calls that matter. With the CLI's hooks, that means:

* **An edit outside a scope you agreed.** Codex applies edits inside the project without asking, so Pushary asks in Always and When away, and refuses in Alert only and In agent.
* **Full access.** When Codex's approval policy is `never`, Codex asks nothing itself. From `pushary` 1.9.17, every call your policy gates then goes to your phone, even while you sit at the keyboard, and waits for your policy's timeout. A call nobody answers is refused with "No response within timeout". Only in Always can a rule that approves on timeout let it run after the full wait. In Alert only and In agent, a gated call still runs, and so does any call made while your plan has lapsed or when the hook fails.
* **Always.** Pushary also asks about a destructive or networked command, a patch that deletes a file, and any call that a rule you wrote or a preset you picked covers.
* **When away.** Pushary asks about a destructive command (such as `rm -rf`, a force push, `git reset --hard`, `git clean` or dropping a table) and a patch that deletes a file, because Codex can run those without asking. With the Mac app it shows in the notch while you are at your Mac and goes to your phone when you are not. With the CLI it goes to your phone when you are away from the keyboard. A call nobody answers is refused. With no device connected, or with the CLI while you are at the keyboard, Pushary hands the call back to Codex, which may run it without asking.
* **Codex's own questions.** When Codex asks you something with `request_user_input`, Pushary shows the question in the notch or on your phone, in every mode except Alert only and In agent. A question with choices shows its choices. A question with no choices shows a text field, and what you type goes back word for word, unless it is only the word "defer", which hands the question back to Codex. Your answer goes back to Codex in place of its card. A question with one choice, more than four, or a secret stays in Codex, and so does any question nobody answers, a blank answer, or a question Pushary cannot reach. On a Mac wired by the Pushary app, several questions asked together stay in Codex when one of them has no choices, until the app is on a version that shows them.

Everything else is left to Codex, which raises `PermissionRequest` when it wants a person. The Mac app's hooks ask about the same calls.

## Check that it works [#check-that-it-works]

```bash
npx pushary@latest doctor
```

Doctor checks your key and plan, then Codex's MCP server, hooks, hook trust, skill and `AGENTS.md` block. It sends a real test push. Add `--roundtrip` to send a test question too.

Then try a real approval. A new workspace starts on **Always**, so it reaches your phone while you sit at your computer. If yours is on When away, decisions stay in Codex's own prompt while you are there. To test from your desk, open the Pushary app, tap **Questions & approvals** at the top of your inbox, and pick **Always**.

Ask Codex to run a command that needs approval, for example `curl -I https://example.com`. Your phone buzzes. Tap **Approve** and Codex runs it. Switch back to **When away** when you are done.

### Check that a call is gated, not just notified [#check-that-a-call-is-gated-not-just-notified]

A question that reaches your phone proves the phone part. It does not prove Codex waits for you before a call runs. Before a team relies on a rule, run each case below once.

Keep Codex on its default approvals, not full access, which changes what asks. Run the last two cases in **Always**, as above. In When away, Pushary keeps a call in Codex while you are at the keyboard, and with the CLI that includes the few minutes after you send a prompt, so a test from your desk never reaches your phone.

| Case                                | What to do                                                                                                                                      | It passes when                                                                                                                                                                                                                                                                                                                                                                      |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Codex older than 0.122              | Run setup.                                                                                                                                      | Never. This Codex only sends notifications, and setup ends with **Codex approvals are not on yet**. Upgrade Codex and run setup again.                                                                                                                                                                                                                                              |
| Hooks not trusted yet               | Open Codex once, then run `npx pushary@latest doctor` in a terminal.                                                                            | Only once doctor's **Codex: hooks trusted** line has a ✓ and doctor asked Codex itself. If doctor says it is checking Pushary's own record instead, the ✓ does not count. With only the ChatGPT app, doctor cannot ask Codex, so open Settings, then **Hooks**, and check that the Pushary hooks are on and trusted. A test question that reached your phone does not count either. |
| `PermissionRequest`, nobody answers | Ask Codex to run `touch ~/pushary-gate-test`. Codex asks about it because the file is outside the project. Leave the phone question unanswered. | Your phone was asked first. After the rule's timeout, the call waits at Codex's own prompt and the file does not exist yet, unless your rule approves or denies on timeout. A yes typed there comes from the person at the terminal, not from the approver. Activity can show the step was left to the terminal, but not who said yes.                                              |
| `PreToolUse`, nobody answers        | In a throwaway git repository with a change in it, ask Codex to run `git reset --hard`. Leave the phone question unanswered.                    | Your phone was asked first. After the rule's timeout, at most about 170 seconds, the call is refused and the change is still there, unless your rule approves on timeout. It does not go to Codex's prompt like the row above.                                                                                                                                                      |

When away ends these two cases the same way once Pushary sees you away, except that a `PermissionRequest` goes back to Codex after the shorter When away window (`npx pushary@latest wait status` shows it).

For each case, write down Codex's version, `npx pushary@latest --version`, the mode, the rule and its timeout, which hook asked, who answered and whether the call ran. On a team, do this before you route a real `git push`.

## What Pushary can and cannot enforce on Codex [#what-pushary-can-and-cannot-enforce-on-codex]

* **Matched:** shell commands (`Bash`), `apply_patch` edits and MCP calls (`mcp__*`). An `apply_patch` call maps to your `Edit` rules, and a multi-file patch is checked against every path it touches.
* **Not matched:** other Codex tools.
* **Not visible to hooks:** Codex sends no `PreToolUse` event for input written into a shell session that is already running.
* **Fails open:** if the hook command crashes, Codex does not block the call.
* **Trust first:** Codex never runs an untrusted hook.

Treat enforcement on Codex as a guardrail, not a security boundary.

### Codex's permission mode decides first [#codexs-permission-mode-decides-first]

Codex reports the same permission modes as Claude Code (`default`, `acceptEdits`, `plan`, `dontAsk`, `bypassPermissions`), and Pushary applies the same rule. It steps aside in `plan` and `dontAsk`, and keeps gating in `default`, `acceptEdits` and `bypassPermissions`. In `acceptEdits`, edits still gate, because Codex sends them as `apply_patch`. The kill switch, your deny rules and a scope you approved apply in every mode. Set `PUSHARY_RESPECT_PERMISSION_MODE=0` to gate in every mode.

When Codex's auto-reviewer approves calls for you, Pushary checks the kill switch and always-deny rules, then lets the reviewer decide. If the reviewer denies a shell command, the `Stop` hook can ask you about it. A yes tells Codex to run the same command again, and that retry is allowed.

## Set it up by hand [#set-it-up-by-hand]

Use setup if you can. It writes the trust entries, so you skip the `/hooks` step.

### MCP server [#mcp-server]

Add this to `~/.codex/config.toml`:

```toml
[mcp_servers.pushary]
url = "https://pushary.com/api/mcp/mcp"
default_tools_approval_mode = "approve"

[mcp_servers.pushary.http_headers]
Authorization = "Bearer pk_xxx.xxx"
```

Or register it with the Codex CLI. This form reads the key from the environment of the shell that starts Codex, so it fails with 401 when that shell never loaded your profile:

```bash
codex mcp add pushary --url https://pushary.com/api/mcp/mcp --bearer-token-env-var PUSHARY_API_KEY
```

Save your key in your shell profile (`~/.zshrc` or `~/.bashrc`):

```bash
export PUSHARY_API_KEY="pk_xxx.xxx"
```

### Hooks [#hooks]

Install the Pushary CLI globally, unless `which pushary-codex-hook` already prints a path. An existing install under `@pushary/agent-hooks` provides the same commands, and npm refuses to install over them.

```bash
npm install -g pushary
```

Add the entries to `~/.codex/hooks.json`. Replace `/path/to/pushary-codex-hook` with the output of `which pushary-codex-hook`, and use the same path in every entry:

```json
{
  "hooks": {
    "PermissionRequest": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 180, "statusMessage": "Waiting for your phone" }] }
    ],
    "PreToolUse": [
      { "matcher": "Bash|apply_patch|mcp__.*|request_user_input|request_user_input_async", "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 180, "statusMessage": "Checking Pushary policy" }] }
    ],
    "PostToolUse": [
      { "matcher": "Bash|apply_patch|mcp__.*", "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 10 }] }
    ],
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 10 }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook --hold-budget=180", "timeout": 180 }] }
    ],
    "SessionStart": [
      { "matcher": "startup|resume|clear", "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 10 }] }
    ],
    "SessionEnd": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 3 }] }
    ],
    "SubagentStart": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 5 }] }
    ],
    "SubagentStop": [
      { "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 5 }] }
    ],
    "PreCompact": [
      { "matcher": "manual|auto", "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 5 }] }
    ],
    "PostCompact": [
      { "matcher": "manual|auto", "hooks": [{ "type": "command", "command": "/path/to/pushary-codex-hook", "timeout": 5 }] }
    ]
  }
}
```

Codex caps `SessionEnd` at 3 seconds, which is why that entry is shorter. Open Codex, type `/hooks` and trust the Pushary entries. In CI, where nobody can answer the trust prompt, Codex supports `--dangerously-bypass-hook-trust`. Use it only there.

## Legacy notify handler [#legacy-notify-handler]

Codex versions before 0.122 have no hooks. On those, setup adds the `pushary-codex` notify handler to `~/.codex/config.toml`. It reports turn completions only, because Codex's notify setting fires for nothing else. It has no policy engine, no kill switch and no session tracking. Setup removes it once you upgrade Codex and run setup again.

## Troubleshooting [#troubleshooting]

### Approvals never reach my phone [#approvals-never-reach-my-phone]

* Codex does not trust the hooks yet. Type `/hooks` in Codex and trust the Pushary entries.
* Your Codex is older than 0.122, so setup installed only the notify handler. Upgrade Codex and run setup again.
* You are at your computer in When away mode, so the decision stays in Codex's prompt. To test, set **Questions & approvals** to **Always** in the app.
* Run `npx pushary@latest doctor`. It reports whether Codex lists and trusts the Pushary hooks.

### Setup says your Codex install is missing the binary it runs [#setup-says-your-codex-install-is-missing-the-binary-it-runs]

A Pushary setup before 0.71.0 asked Codex for its version. On macOS that hands the binary to XProtect, which can quarantine the Codex CLI. Setup no longer runs Codex. Reinstall it:

```bash
npm install -g @openai/codex
```

### The Pushary MCP server answers 401 [#the-pushary-mcp-server-answers-401]

You probably added it with `--bearer-token-env-var`, and Codex started from a shell without `PUSHARY_API_KEY`. Run setup again. It writes the key into `config.toml`.

### Setup says Pushary for Mac handles Codex's approvals [#setup-says-pushary-for-mac-handles-codexs-approvals]

The Mac app already owns Codex's hooks. Setup reuses the app's key and keeps the app's hooks. To have the CLI own them instead, run `npx pushary@latest setup --take-over-hooks`.

### More help [#more-help]

* [Codex hooks](/codex-hooks): the events, the trust hash, and how hooks meet the auto-reviewer.
* [Codex dangerously skip permissions](/codex-dangerously-skip-permissions): the flags and config keys that turn approvals off, and a safer pattern.
* [Control Codex from your phone](/control-codex-from-phone): approve, answer or stop a Codex session away from the terminal.

## Next steps [#next-steps]

<Cards>
  <Card title="Set your approval rules" description="Choose which actions run on their own and which wait for you." href="/docs/agents/policies" />

  <Card title="Codex notifications" description="Every way to get Codex on your phone, and what each one covers." href="/codex-notifications" />

  <Card title="Stop a session with the kill switch" description="Stop one agent session, or all of them, from your phone." href="/docs/agents/kill-switch" />
</Cards>