Codex Status Light: One Physical Signal for AI Work Across Multiple Macs
How I built a local-first macOS and ESP32 status light, then added an optional private relay so Codex sessions from multiple Macs can share one physical display without uploading raw rollout files.
Codex Status Light: one physical signal for AI work across multiple Macs
Codex Status Light makes concurrent AI coding work visible away from the screen. A native macOS menu-bar app watches Codex sessions running in separate VS Code windows, interprets what each session is doing and assigns it a stable physical LED on an ESP32-controlled WS2812B strip.
The system is local-first. A Mac and a Codex Light can communicate directly over the LAN with no account or external dependency. For work spread across several computers, each Mac can instead publish its latest interpreted session state to a privately hosted relay. One paired Codex Light subscribes to the combined stream and displays work from every signed-in machine.
The idea came from a practical problem. Once several coding agents are working at the same time, it becomes difficult to see which session is thinking, running a tool, waiting for approval, finished, or failed without repeatedly switching between windows. Notifications can announce individual events, but they do not provide a persistent view of the whole workspace.
I wanted the result to feel more like ambient infrastructure than another dashboard: glanceable, stable and quiet. Adding a server could not mean abandoning that principle. Rollout parsing remains on each Mac, raw JSONL files are never uploaded, direct LAN mode remains available, and the relay stores only the latest bounded session snapshot needed to coordinate multiple machines.
Project overview
| Area | Implementation |
|---|---|
| Mac application | Native SwiftUI menu-bar app for macOS |
| Session source | Local Codex rollout-*.jsonl event files |
| Session tracking | Independent file/session identity with incremental parsing |
| Hardware | ESP32-WROOM-32, WS2812B LEDs and optional SSD1306 OLED |
| Direct device API | Local HTTP using GET /health and POST /state |
| Multi-machine relay | Node.js, PostgreSQL and secure WebSockets |
| Hosting | Self-hosted VPS deployment through Dokploy |
| Accounts | Web dashboard with separate Mac and device credentials |
| Discovery | mDNS hostname with resolved-IP reuse and fallback |
| Wi-Fi setup | Protected fallback hotspot and captive setup page |
| Distribution | Universal, Developer ID signed and notarized macOS app |
| Data boundary | Parsing stays local; raw rollout files never leave the Mac |
| Device payload | LED states, brightness and bounded display labels; never raw rollout data |
The problem
A useful status light has to answer more than whether a process exists. It needs to represent the current meaning of an evolving event stream.
For each live session, the system needs to distinguish between:
- idle;
- working or thinking;
- running a tool or terminal command;
- waiting for approval or user input;
- completed;
- error;
- aborted.
It must do that for several sessions at once, including sessions opened in different VS Code windows but using the same project directory. An LED also needs to retain its identity while a session is active. If lights move whenever the list is refreshed or reordered, the physical display becomes harder to understand than the interface it is meant to simplify.
The scope later grew beyond one computer. I use Codex in VS Code on multiple Macs, but wanted one physical device to represent all of them. That introduced account ownership, authentication, machine identity, cross-machine LED allocation, reconnect behaviour and a new privacy boundary—without making the relay mandatory for someone using a single Mac on one LAN.
The embedded side introduces a different set of requirements. The device has to recover when Wi-Fi changes, provide a clear setup path without recompiling credentials, expose a small dependable API, and make its own connection state visible even when the Mac cannot reach it.
Parsing an evolving local event stream
Codex writes newline-delimited JSON rollout files beneath the user's local sessions directory. These files are useful integration points, but they are event logs rather than a permanently frozen application API. Event envelopes and payload shapes can evolve, and ordinary message text may contain words such as “error”, “complete” or “approval” without representing a state transition.
I built the parser around structured event and payload types instead of searching arbitrary text. Recognised lifecycle events update the session state, while human-readable activity is extracted separately for the menu-bar display. The parser tolerates common shape variations and ignores malformed or incomplete records rather than allowing one line to stop monitoring.
The monitor reads incrementally. It stores a byte offset and any unfinished trailing record for each rollout file, then consumes only newly appended data on the next scan. This avoids repeatedly parsing complete session histories and correctly handles a JSON record that is still being written when the app checks the file.
Tests cover representative task lifecycle events, misleading message content, partial JSONL records, and the state transitions the LEDs depend upon. That testable boundary is important because a plausible-looking parser can still produce distracting physical behaviour when it misclassifies one event.
Keeping simultaneous sessions separate
Working directory is display metadata, not session identity.
Two VS Code windows may legitimately run separate Codex sessions against the same repository. Merging them by directory would lose activity and cause one session to overwrite the other's LED state. The app instead derives identity from the rollout file and session metadata, allowing sessions to share a project name while remaining independent.
LED assignment is equally deliberate. Existing assignments are preserved while sessions remain eligible, and free LEDs are assigned to newer active sessions. When capacity is reached, an active session can reclaim a light from an idle session, but active sessions are not shuffled simply because the menu list changes order.
The menu-bar interface reinforces that mapping. Each row shows the project, a short session identifier, current state, recent activity, assigned LED number, elapsed time and last update. This provides enough detail to verify what the physical display means without turning the utility into another full development environment.
Extending local-first operation across multiple Macs
Direct mode remains the shortest path: the Mac creates a complete LED snapshot and sends it to POST /state on the local Codex Light. It works without the internet, a user account or the relay.
Relay mode adds coordination rather than moving session interpretation into the cloud. Each signed-in Mac has its own persistent UUID and a machine-scoped access token stored in macOS Keychain. It publishes only its latest interpreted sessions: session identity, state, project label, recent activity and timestamps. The server never receives or reads the underlying rollout files.
The privately hosted Node.js service combines current snapshots from every online Mac belonging to the same account. Session identity becomes machine UUID + session ID, so even matching session identifiers on two computers remain independent. The allocator preserves existing LED leases across updates and produces one complete state for every paired Codex Light.
The ESP32 uses a device-scoped token obtained through a short pairing-code flow. It opens an outbound authenticated secure WebSocket to the relay, so the VPS never needs inbound access to the home LAN. When any Mac changes state, the relay recomputes the aggregate snapshot and pushes it to the connected light. Machine expiry removes stale work when a laptop sleeps or disappears, while reconnecting devices receive the latest complete snapshot immediately.
The same account also provides a live web dashboard. It shows active sessions from all online Macs, their project and activity, the source machine and assigned LED number. Users can pair or revoke Macs and Codex Light devices, see connection state, set brightness independently and choose whether devices mirror one stream or extend it. Two eight-session-LED lights can therefore show the same LEDs in two places or form one stable 16-LED layout. Each physical device still receives an ordinary local snapshot containing LED indices, semantic states, brightness and short bounded project, activity and machine labels for the optional OLED. It never receives raw rollout records. The relay is designed for self-hosting on a VPS with Dokploy, PostgreSQL persistence and TLS termination.
Designing a small, predictable device API
Both operating modes use snapshots rather than individual LED commands. In direct mode, every changed POST /state payload describes the complete configured session-light state, including LEDs that should be idle. In relay mode, the same semantic model travels over the authenticated WebSocket. Snapshot semantics make recovery straightforward: after a reconnect or device restart, one message restores the intended display without replaying an event history.
The firmware owns animation and colour. The Mac sends semantic states such as working, tool, waiting or complete; the ESP32 turns those into pulses, flashes and solid colours. This keeps network traffic small and allows the LEDs to animate smoothly even if the Mac is briefly busy.
A separate final pixel is reserved for the device itself. With eight session LEDs, physical LED nine shows:
- red while the ESP32 is disconnected or starting;
- blue while the Wi-Fi setup hotspot is active;
- green when Wi-Fi is connected and the selected output path is ready—either the local HTTP API or the authenticated relay stream.
The status LED is firmware-owned and cannot be overwritten by a session snapshot. Idle session LEDs retain a faint blue glow, making the strip visible without competing with active states.
An optional 128×64 SSD1306 OLED adds detail without changing the core build. The firmware detects it over I²C at startup and remains fully functional when it is absent. It shows Wi-Fi setup instructions and relay pairing codes, then rotates through active sessions with their LED number, state, project, source machine and recent activity. This keeps the LEDs ambient while making the device more informative when viewed up close.
Making Wi-Fi setup recoverable
Hard-coded Wi-Fi credentials are convenient during development but unsuitable for a device that may move between networks. The firmware now stores credentials locally in the ESP32's non-volatile storage and attempts to reconnect automatically.
If no credentials exist, connection fails during startup, or the device remains disconnected, it creates a protected CodexLight-XXXXXX access point. A small captive setup page lets the user select a network and enter its password. The credentials remain on the device, and the ESP32 restarts into station mode after saving them.
The existing GET /health and POST /state endpoints remain stable, including when relay support is enabled. Health responses also describe the number of session LEDs, total physical LEDs, status LED index, Wi-Fi mode and current IP address. That additional metadata makes the system easier to inspect without breaking the Mac client's original assumptions.
Hardening local networking on macOS
The friendly codex-light.local hostname keeps setup approachable, but repeated mDNS queries can become noisy when the device is asleep or temporarily unreachable. The app therefore resolves the hostname through a successful health request, reuses the IP address returned by the device, and falls back to mDNS if that address later fails. Health probes back off while the device is unavailable, and edits to the Host field are debounced so every keystroke does not create a new lookup.
State requests are deduplicated, identical snapshots are not resent after success, and concurrent health checks are prevented. Short timeouts keep the menu-bar app responsive, while a successful reconnect clears the snapshot cache so the current LED state is sent again.
Relay mode follows the same principle at a different boundary. Macs send periodic heartbeats and only replace their latest server-side snapshot when the revision advances. The server expires silent machines, isolates data by account and issues separate revocable credentials to every Mac and light. TLS protects internet transport, while the ESP32 initiates its own outbound connection and automatically reconnects with backoff.
Debugging the complete hardware path
One useful lesson from the build was the value of testing the whole signal path rather than assuming a wiring fault or a software fault too early.
The first WROOM prototype flashed successfully, joined Wi-Fi and accepted API requests, but the LEDs remained dark on GPIO4. I added a clear red, green and blue startup test, captured the serial boot output, verified the HTTP health response, and sent a forced green snapshot. Moving the data connection to the non-strapping GPIO18 immediately brought the strip to life.
GPIO18 became the documented default, and the startup test remains in the firmware as a simple diagnostic. On reset, it confirms power, data direction, pixel protocol and firmware execution before Wi-Fi becomes relevant.
Preparing the Mac app for distribution
The finished utility is a native menu-bar application rather than a terminal process. Settings cover direct or account-relay operation, account sign-in, the local Codex Light host, session LED count, brightness, completed-state duration, session expiry and launch at login.
I also addressed the less visible details needed for a distributable Mac product: a dedicated app icon and asset catalogue, a stable bundle identifier, Hardened Runtime without unnecessary security exceptions, Developer ID signing, Apple notarisation and a universal binary for both Apple Silicon and Intel Macs.
Direct distribution is the most appropriate initial route because the app needs to monitor ~/.codex/sessions. A Mac App Store version would require App Sandbox plus an explicit folder picker and persistent security-scoped bookmark. Keeping that distinction clear avoids weakening the product simply to make the current build fit a different distribution model.
The result
Codex Status Light turns an otherwise hidden local event stream into a dependable ambient display:
- multiple simultaneous sessions remain distinct, even in the same repository;
- LED allocation stays stable while a session remains active;
- structured parsing distinguishes work, tools, waiting, completion and failure;
- the Mac sends complete, recoverable state snapshots;
- the ESP32 handles animation independently;
- a dedicated pixel shows device connectivity without needing the Mac app;
- Wi-Fi can be reconfigured through a local fallback hotspot;
- direct mode continues to work entirely on the local network;
- an optional private relay combines sessions from several Macs onto one light;
- raw rollout files remain local even when relay mode is enabled;
- machine and device credentials can be revoked independently;
- the web dashboard shows the same aggregate stream and controls per-device brightness plus mirrored or extended layouts.
The finished system is intentionally modest in scale, but it combines product design, native macOS development, event-stream parsing, embedded firmware, local networking, authenticated cloud infrastructure, hardware debugging and release engineering in one coherent build. Most importantly, it solves the original problem without demanding more attention: whether the work is happening in several windows or on several Macs, a glance at the desk is enough to understand what the active coding sessions need next.
What I would build next
The next improvements would focus on convenience while preserving the local-first architecture:
- Bonjour service discovery so devices can be selected without entering a hostname.
- Signed over-the-air firmware updates on the local network.
- Optional project-specific colours or persistent project-to-LED preferences shared through the relay.
- Account-level device naming and project-specific display controls in the dashboard.
- A compact enclosure and diffuser designed around the session and device-status layout.
- A sandboxed folder-selection flow if Mac App Store distribution becomes worthwhile.
Codex Status Light is an independent utility built around local Codex session files and is not affiliated with or endorsed by OpenAI.