Codex Agent Indicator
Use the five G-keys on a Logitech G915 keyboard as a live Codex Desktop task monitor.
What the lights mean
| Colour | Status |
|---|---|
| 🔵 Blue | Codex is working |
| 🟠Amber | Codex needs approval |
| 🟣 Purple | Codex needs your input |
| 🟢 Green | Codex finished successfully |
| 🔴 Red | Codex stopped with an error |
G1 through G5 each represent one top-level task from the Codex desktop app.
Active keys flash brightly while the rest of the keyboard stays on with a dim,
steady background. Codex CLI, codex exec, Claude Code integrations, other
apps, and ephemeral tasks do not occupy G-keys.
The indicator intentionally uses only G1 through G5. It leaves F1 through F12, M1 through M3, MR, media controls, macros, and onboard profiles untouched.
Green, red, purple, and amber stay visible until you press their G-key. The key brings Codex to the foreground, selects the matching task in its sidebar, and then clears the acknowledged light. A blue key stays blue because its task is still working.
Requirements
- macOS
- Logitech G915 connected through its wired USB HID interface
- Rust 1.91 or newer
jqfor safely merging Codex hooks
If jq is missing and you use Homebrew:
Install
Install the CLI from crates.io
Install the published command-line binary:
Cargo installs the command in ~/.cargo/bin. This gives you the CLI, but it
does not add Codex hooks or create the macOS LaunchAgent.
After installation, update to the latest published release with:
The updater uses Cargo to replace the binary in its current install root. This
means a complete ~/.local/bin setup stays in ~/.local/bin instead of gaining
a second copy in ~/.cargo/bin. It preserves your configuration and hooks, and
restarts the keyboard indicator service when it was installed by the complete
setup below.
To reinstall a specific version instead:
Complete keyboard-monitor setup
Clone or download this repository, open Terminal in its folder, and run:
The installer:
- builds the optimized Rust binary;
- installs it in
~/.local/bin; - creates a private user configuration;
- safely adds the required hooks to
~/.codex/hooks.json; - starts a lightweight macOS LaunchAgent.
It preserves unrelated hooks already present in your Codex configuration.
After the first install, open /hooks in Codex and trust this command:
~/.local/bin/codex-agent-indicator hook
You do not need to restart Codex.
The complete installer builds the same version from the checked-out source and is recommended for first-time setup because it also configures the Codex hooks and macOS LaunchAgent.
Uninstall
From the repository folder, run:
This removes the daemon, binary, and only this project's hook entries. Your custom configuration is kept. To remove that too:
Use it
Press an illuminated G-key to bring Codex forward and select its matching task in the sidebar.
Useful commands:
You can also preview or clear states manually:
If your shell cannot find the command, add this to your shell profile:
Customize the lights
Edit:
~/.config/codex-agent-indicator/config.toml
The daemon automatically reloads valid changes. The main settings are:
[]
= "#101820"
= true
= 500
= 20
= 1000
[]
= "#007aff"
= "#ff9500"
= "#af52de"
= "#34c759"
= "#ff3b30"
-
Change
backgroundto control the steady light on every non-indicator key and every unoccupied G-key. It does not change active G1-G5 status colours. Useful six-digit RGB values are:#101820— very dim;#202c38— readable in a dark room;#304050— brighter.
-
Increase
flash_interval_msfor slower flashing. -
Keep
flash_dim_percentabove zero so a delayed background process cannot leave active indicators looking completely dark. -
Set
flash_enabled = falsefor steady status colours. -
Keep
reassert_interval_ms = 1000for the fastest supported recovery when G HUB or another lighting app takes control. Increase it for less frequent direct-lighting watchdog refreshes. -
Set
navigation.enabled = falseif G-keys should show status without opening tasks. -
Set
detect_questions = falseto treat every stopped turn as completed.
Behaviour
- The first five top-level Codex Desktop tasks use G1 through G5.
- A task is admitted only when its matching persisted journal identifies
Codex Desktopas its origin. Codex CLI,codex exec, Claude Code integrations, other apps, ephemeral sessions, and standalone subagent sessions are ignored. - Child activity within an admitted desktop task can update its parent light, but never receives a separate G-key.
- Fast hooks are reconciled against the known task's local
task_startedandtask_completejournal records. A missed terminal hook therefore corrects itself within about 250 ms instead of leaving a stale blue key. - Permission and user-input states take priority over unrelated subagent activity.
- A failed individual tool remains blue while Codex handles it; red is reserved for a terminal turn failure.
- Finished and attention states are never removed by a timer.
- Current Codex Desktop task lights are restored after the daemon or Mac restarts. Missing journals and old non-app slots are pruned before the keyboard is painted, preventing ghost keys.
- A low-rate watchdog reasserts direct lighting mode after keyboard sleep or another lighting app takes control.
- Status-cache write failures are recorded without stopping live lighting or G-key navigation. The daemon reports the last status-write and G915 failures after recovery so intermittent outages remain diagnosable.
codex-agent-indicator statusalso reports successful lighting reassertion count/timing and event-loop delays of at least 250 ms. Routine successful reassertions stay quiet and do not force additional status-file writes; repeated loop-delay logs and writes are limited to once every 30 seconds.- The
statuscommand asks the daemon for a fresh, state-neutral snapshot before reading the local cache, so diagnostics are current without adding background polling. - A newer task cannot displace an unacknowledged green, red, purple, or amber state.
- If all five keys need acknowledgement, open one before another task can be assigned.
- The oldest blue working slot may be reused when all keys are occupied.
- G-key navigation explicitly targets the Codex app, brings it to the foreground, and selects the task through its local deep link.
- A task is acknowledged only after its Codex deep link opens successfully.
- Merely ending a Codex process does not clear an unacknowledged result.
Troubleshooting
Run:
Common causes:
- The G915 must expose the expected wired USB HID interface.
- Logitech G HUB may overwrite the indicator colours if it is running an active lighting effect.
- Codex may ask you to trust the hook command after installation.
- G1 through G5 intentionally ignore tasks started from Terminal,
codex exec, Claude Code, or another app. Use Codex Desktop when a task should appear on the keyboard and be reopenable by pressing its G-key. - A terminal
task_completerecord repairs a missedStophook. An app-level failure that writes neither record can still leave the last blue state; usecodex-agent-indicator set error TASK_IDto correct that rare case manually. - The daemon log is stored at
~/Library/Logs/codex-agent-indicator.log. Hardware and persistence recovery events include Unix timestamps.event-loop-delayidentifies CPU scheduling or slow local work; a recent successful lighting reassertion with no loop, HID, or persistence failure points to another lighting app taking control.
Privacy and performance
- Everything runs locally on your Mac.
- There is no telemetry, analytics, cloud service, or network server.
- Hook messages travel through a private user-only Unix socket.
- Every automatic hook is checked against the matching journal's bounded metadata record. Only a top-level Codex Desktop task is admitted; unknown or missing metadata fails closed.
- The daemon follows only the local journals for tasks currently assigned to G1 through G5. It reads at most 256 KiB to classify a journal, scans at most the latest 8 MiB once after a restart, then checks only for appended bytes every 250 ms.
- It ignores non-lifecycle journal records and never stores journal content in its status file or logs.
- The daemon does not poll Codex databases or processes and does not start a second app-server.
- The LaunchAgent uses macOS's interactive scheduling class because it handles physical G-key presses. It remains event-driven, keeps filesystem I/O at low priority, and normally uses only a small fraction of one CPU core.
- Only the tail of a final assistant message is inspected to distinguish a question from a completed response.
- No Accessibility permission, screen recording, browser control, MCP server, Codex plugin, or extra background app is required.
- Repository templates contain placeholders rather than usernames or personal computer paths.
How it works
The project is one small native Rust binary. It serves as:
- the background daemon;
- the fast Codex hook forwarder;
- the G-key task switcher;
- the configuration and diagnostic command.
Hooks send a small Unix datagram and exit. The daemon verifies the task's
persisted Codex Desktop origin, tracks task, turn, and child-agent identity,
batches lighting changes into one HID++ frame, debounces unchanged status-file
writes, restores only valid app tasks after restarts, and sleeps between events.
A bounded local-journal adapter reconciles native turn start and completion
records when a hook is missed. A low-rate watchdog reclaims Logitech
direct-lighting mode. G-key presses use Logitech's HID++ 0x8010 feature. Task
switching uses Codex's codex://threads/<thread-id> deep link.
Only live RGB output and G-key notification diversion are controlled while the daemon runs. The program does not edit G HUB macros, profiles, key assignments, or onboard memory.
Development
Lifecycle replay tests use privacy-scrubbed, version-labelled Codex hook JSON
and the matching official input schemas. A separate pinned Codex app fixture
exercises origin admission and native task_started/task_complete
reconciliation, including rejected CLI/other-app sessions, missing journals, a
missed Stop, and a newer active turn. Together they exercise the complete
adapter → lifecycle tracker → G-key slot path:
When the Codex hook schema changes, add a new fixture snapshot instead of rewriting the historical one.
The project intentionally does not require a formatter pass for validation.
License
Licensed under either of:
at your option.