Claude Code Hooks 配合 Stream Deck,顯示哪些工作階段正在等候我
Which Session Is Waiting for Me? Claude Code Hooks on a Stream Deck
作者用 Claude Code Hooks、JSON 狀態檔和 Stream Deck Neo 製作工作階段提示器,以按鍵顏色顯示工作中、等候批准、已完成或閒置。按下按鍵會將相應工作階段的視窗帶到前景,狀態檔不包含提示詞或工具輸出。作者修正 Windows 上讀取與檔案改名同時發生時可能導致狀態未更新的問題,方法是短暫等待後重試。
I usually have several Claude Code sessions running side by side — a document here, a small fix there, some research. On a busy day, around five.
Delegating the work wasn't the problem. Noticing was:
- A session asking "may I run this?" gets buried behind other windows.
- A session finishes, and I don't notice for a while.
- I keep switching windows just to check.
The AI wasn't the slow part. The time until I noticed was. The more sessions I ran, the worse it got.
The setup: three small pieces
I put the state of each session on a small button box on my desk (a Stream Deck Neo: 8 keys and a thin info bar).
- Claude Code hooks run a small script at fixed moments — a prompt is submitted, a tool is about to run, permission is requested, the turn stops.
- The script writes one tiny JSON file per session: which folder, which session, which state. No prompt text, no tool output.
- A Stream Deck plugin reads those files every second and colors the keys.
The hook side is mostly a mapping from hook events to four states:
// hook events -> key state
function stateFor(d) {
switch (d.hook_event_name) {
case "UserPromptSubmit":
case "PostToolUse":
return "running"; // blue
case "PreToolUse": // asking me a question also counts as waiting on me
return ["AskUserQuestion", "ExitPlanMode"].includes(d.tool_name) ? "ask" : "running";
case "PermissionRequest":
return "ask"; // red
case "Notification":
if (d.notification_type === "permission_prompt") return "ask";
if (d.notification_type === "idle_prompt") return "waiting";
return null;
case "Stop":
return "waiting"; // orange: done, please look
case "SessionStart":
return "idle"; // gray
default:
return null;
}
}
The colors:
| Color | Meaning |
|---|---|
| Blue | Working |
| Red | Waiting for my permission |
| Orange | Done — please look |
| Gray | Idle |
Pressing a key brings that session's window to the front. See red, press it, approve in that window. No more hunting.
Where it broke
The bug that took longest: a session finished, but its key stayed blue.
The hook writes to a temp file and renames it over the real one. The plugin reads that file every second. On Windows, if the rename lands at the exact moment the plugin is reading, the rename can fail with EPERM. My first version silently swallowed that error — so the "done" state was never written, and the key stayed blue. By the time I found it, 28 orphaned temp files had piled up.
The fix is boring: retry a few times with a short wait.
function replaceFile(tmp, file) {
for (let i = 0; ; i++) {
try { fs.renameSync(tmp, file); return; } catch (e) {
if (i >= 9) { try { fs.unlinkSync(tmp); } catch {} throw e; }
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 30); // ~30 ms
}
}
}
Boring, but necessary. A status light you can't trust is just decoration.
Smaller one: after /clear, the session continues under a new ID, so I briefly had two keys with the same name. Now the hook removes the old record when a session starts from a clear, and keeps an old-ID → new-ID map for the plugin.
Then a character moved in
Colors alone felt a bit dry, so I drew a pixel-art chick into the keys. Her name is Aqui (from Aquila, the eagle constellation).
- Working: juggling a ball
- Waiting for permission: holding up a "?"
- Done: a "!"
- Idle: eyes closed, "z"
If the next key is also working, she passes the ball over. I wanted a box I like looking at, not just another notification.
That turned into a series of YouTube Shorts — over 20 episodes now. These days she rides around in a pixel-art WRX S4 through the seasons.
- Shorts playlist: https://www.youtube.com/playlist?list=PLOctjyutDnug
- How it works (2:25, Japanese with visuals): https://www.youtube.com/watch?v=m7cUQ3Owqv0
If you only do one thing
Put "waiting for permission" somewhere you'll see it. It doesn't need a Stream Deck — a colored corner of the screen or a sound works. For me, the time a session spent waiting on me was the least visible and the most wasted.
I haven't measured how much time this saves, so I won't claim a number. What I can say is that I stopped hunting through windows.
A personal project by a Claude Code fan. Not affiliated with Anthropic or Elgato.
來源:DEV Community · dev.to