A physical status board for Claude Code built on an Arduino Mega. Each of 4 "slots" has a red + green LED pair that reflects the live state of a Claude Code session: idle, working, waiting for you, or needing your attention.
┌─────────────────────────────────────────┐
│ [G][R] [G][R] [G][R] [G][R] │
│ slot 1 slot 2 slot 3 slot 4 │
└─────────────────────────────────────────┘
- You run
claudein a terminal. - Claude Code fires hook events (
SessionStart,PreToolUse,Stop,Notification,SessionEnd, ...) — configured in~/.claude/settings.json. - Each hook pipes its JSON payload into
claude_led_bridge.py. - The bridge maps the event to an LED state, assigns the session a slot (1–4, first-come-first-served), and sends
<slot>:<state>\nover USB serial to the Arduino. - The Arduino sketch renders the state on the LEDs.
Up to 4 simultaneous Claude Code sessions can be tracked at once.
| State | LEDs | Meaning | Triggered by |
|---|---|---|---|
off |
both off | no session | SessionEnd |
idle |
both blink slowly | session open, nothing happening | SessionStart |
working |
green solid | Claude is thinking / using tools | UserPromptSubmit, PreToolUse, PostToolUse |
waiting |
red solid | turn ended, waiting on your reply | Stop |
alert |
red blinks fast | notification — needs input NOW | Notification |
-
Board: Arduino Mega 2560 (pins 22–29 are digital-only, no PWM needed).
-
LEDs: 4 × red, 4 × green (8 total), plus current-limiting resistors (≈220 Ω).
-
Pin mapping (set in the sketch):
Slot Green pin Red pin 1 22 23 2 24 25 3 26 27 4 28 29
Wiring per LED: pin → 220Ω → LED anode → LED cathode → GND.
| Path | What it is |
|---|---|
claude_led_dashboard.ino |
Arduino sketch — listens on serial, drives the LEDs |
claude_led_bridge.py |
Python hook script — stdin JSON → serial command |
settings.json |
Reference Claude Code hooks config (merge into ~/.claude/settings.json) |
.venv/ |
Local venv holding pyserial (not committed) |
Runtime state lives outside the repo:
~/.claude-led/state.json— session-ID → slot mapping~/.claude-led/bridge.log— event log (useful for debugging)
Open claude_led_dashboard.ino in the Arduino IDE, select your Mega and port, and upload. On boot, each LED flashes briefly.
# macOS: prefer /dev/cu.* (non-blocking opens; /dev/tty.* blocks on DCD)
ls /dev/cu.usbmodem* /dev/cu.usbserial* 2>/dev/null
# Linux:
ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/nullCopy the path (e.g. /dev/cu.usbmodem11301 on macOS, /dev/ttyACM0 on Linux).
If $CLAUDE_LED_PORT isn't set, the daemon auto-detects via pyserial VIDs
and falls back to globbing these paths.
git clone <this-repo> ~/claude-led-dashboard
cd ~/claude-led-dashboard
python3 -m venv .venv
.venv/bin/pip install pyserialOn macOS with Homebrew Python you must use a venv — Homebrew's Python blocks system-wide
pip install(PEP 668).
echo 'export CLAUDE_LED_PORT=/dev/tty.usbmodem11301' >> ~/.zshrc
source ~/.zshrcReplace the path with yours from step 2.
Only one process can hold the serial port. If the Serial Monitor is open, the bridge can't talk to the Arduino.
echo '{"hook_event_name":"SessionStart","session_id":"test-1"}' \
| ~/claude-led-dashboard/.venv/bin/python ~/claude-led-dashboard/claude_led_bridge.py
# → slot 1 starts idle-blinking
echo '{"hook_event_name":"PreToolUse","session_id":"test-1"}' \
| ~/claude-led-dashboard/.venv/bin/python ~/claude-led-dashboard/claude_led_bridge.py
# → slot 1 solid green
echo '{"hook_event_name":"Stop","session_id":"test-1"}' \
| ~/claude-led-dashboard/.venv/bin/python ~/claude-led-dashboard/claude_led_bridge.py
# → slot 1 solid red
echo '{"hook_event_name":"Notification","session_id":"test-1"}' \
| ~/claude-led-dashboard/.venv/bin/python ~/claude-led-dashboard/claude_led_bridge.py
# → slot 1 blinking red fast
echo '{"hook_event_name":"SessionEnd","session_id":"test-1"}' \
| ~/claude-led-dashboard/.venv/bin/python ~/claude-led-dashboard/claude_led_bridge.py
# → slot 1 offMerge the contents of settings.json into ~/.claude/settings.json. If you don't have one yet:
mkdir -p ~/.claude
cp settings.json ~/.claude/settings.jsonIf you already have ~/.claude/settings.json, add the hooks block manually.
rm -f ~/.claude-led/state.json
claude # in any directory — slot 1 lights upOpen up to 4 terminals; each new claude session claims the next free slot.
Read the log:
tail -f ~/.claude-led/bridge.logCommon failure modes:
| Symptom | Fix |
|---|---|
serial error: could not open port ... |
Wrong CLAUDE_LED_PORT, or Serial Monitor still open |
serial error: Resource busy |
Another process (another Claude session, or Serial Monitor) is holding the port |
| Log is empty | pyserial not installed in the venv, or venv path wrong in settings.json |
| Hook runs but LEDs don't move | Arduino may have frozen — unplug / replug USB |
Wrong port reported in log (e.g. usbmodem1101) |
Claude was launched before CLAUDE_LED_PORT was exported. Quit and relaunch |
Reset everything:
rm -f ~/.claude-led/state.jsonThat just clears slot assignments; it doesn't touch the Arduino or the config.
The Arduino accepts these over serial (9600 baud, newline-terminated):
| Command | Effect |
|---|---|
<slot>:off |
Slot goes dark |
<slot>:idle |
Slot both-LEDs slow blink |
<slot>:working |
Slot solid green |
<slot>:waiting |
Slot solid red |
<slot>:alert |
Slot red fast blink |
ping |
Arduino replies pong |
reset |
All 4 slots → off |
Where <slot> is 1–4.
MIT.