codex-approval-watcher is a small Rust service for macOS that watches Codex
session logs and emits a synthetic approval.requested event when it sees an
approval prompt. It exists for setups where approval requests are not surfaced
through the normal notify or hooks flow.
- Watches
~/.codex/sessions/**/*.jsonl - Uses
kqueuenotifications plus periodic reconciliation to avoid missed events - Detects approval prompts from tool calls that request escalated permissions
- Sends a local macOS notification with
osascript - Forwards normalized
approval.requestedevents to optional hooks
This project is intentionally focused on approval prompts. It does not try to replace Codex's existing turn-complete notifications.
Install from the published tap:
brew tap danbi2990/tap
brew install codex-approval-watcherStart the background service:
brew services start codex-approval-watcherVerify notifications after install:
/opt/homebrew/bin/codex-approval-watcher doctor-notificationsOn first run the watcher creates:
~/.config/codex-approval-watcher/config.toml
automatically. If you want to inspect the template first, see:
/opt/homebrew/opt/codex-approval-watcher/share/codex-approval-watcher/config.homebrew.toml.example
If you run the CLI without an explicit config path, it uses:
~/.config/codex-approval-watcher/config.toml
Use config.example.toml for local/manual runs, or
inspect config.homebrew.toml.example for the
Homebrew-oriented default layout.
Current config fields:
sessions_root: directory containing Codex session JSONL filesstate_file: persisted offset and metadata cache pathevent_timeout_ms: watcher receive timeout used for responsive shutdownnotifications: built-in local notification settingshooks: optional commands that also receiveapproval.requestedevents
Each hook receives one JSON document on stdin:
{
"event": "approval.requested",
"session_id": "019cf0bd-b079-7b32-b46b-c398698ff9c6",
"cwd": "/path/to/project",
"timestamp": "2026-03-16T00:00:00Z",
"message": "Do you want to allow writing outside the workspace?",
"command": "printf 'hi' > /tmp/example.txt"
}Validate the crate:
cargo checkPrint the bundled example config:
cargo run -- print-example-configValidate a config file:
cargo run -- validate-config ./config.tomlRun the watcher:
cargo run -- runOr pass a config explicitly:
cargo run -- run ./config.tomlSend one test notification:
cargo run -- test-notificationOr pass a config explicitly:
cargo run -- test-notification ./config.tomlSend a notification and verify delivery markers from macOS unified logs:
cargo run -- doctor-notificationsVerification gate for changes in this repository:
cargo test
cargo clippy --all-targets --all-features -- -W clippy::pedanticcargo clippy should be run with clippy::pedantic enabled for local
verification before shipping changes.
Run the end-to-end self-test without launchd:
./self_test.shThat script copies fixture session files into a temporary Codex home, starts
the watcher as a child process, appends a synthetic approval line, and verifies
that the configured hook receives an approval.requested event.
dev/install_service.sh installs a repo-local
launchd agent for development and personal use.
By default it expects ./config.toml in the repository root. You can also pass
a config path explicitly:
cp config.example.toml config.toml
./dev/install_service.sh install
./dev/install_service.sh restart ./examples/alfred-vscode-switcher.example.tomlSupported commands:
installrestartuninstallstatusbuild
The tap formula is based on:
homebrew/codex-approval-watcher.rb.
Typical maintainer release flow:
- Push this repository to GitHub.
- Create a tag such as
v0.1.0. - Build the release tarball and compute its
sha256. - Replace the placeholder
homepage,url, andsha256in the formula. - Copy the formula into a personal tap such as
your-user/homebrew-tap. - Install with
brew install your-user/tap/codex-approval-watcher. - Start the service with
brew services start codex-approval-watcher.