Problem
The "waiting" (yellow) status in the dashboard is the most important signal in the cockpit — it tells the operator that a Claude Code session is blocked waiting for permission approval. This status is driven entirely by the Notification hook event type firing from Claude Code.
Current implementation (server/hooks.py:151-156):
elif event_type == "Notification":
base_updates["status"] = "waiting"
message = event_data.get("message", "")
if message:
base_updates["task_description"] = message
session = await db.update_session(session_id, **base_updates)
The hook is received via POST /api/hooks (server/routes/api.py:73-80) and the status update is broadcast to all dashboard clients via WebSocket (server/routes/ws.py:19-33).
The concern: The Notification event type has been inconsistent across Claude Code versions. If the hook doesn't fire, the session appears "working" when it's actually blocked — the operator has no idea a session needs attention. This is a silent failure with high impact.
What needs to happen
1. Comprehensive test coverage for the Notification hook pipeline
Write end-to-end tests that verify the full path:
- Hook ingestion: Simulate a
Notification hook payload hitting POST /api/hooks and verify the session status transitions to "waiting" in the database.
- WebSocket broadcast: Verify that connected dashboard clients receive a
session_update message with status: "waiting" and the correct task_description when a Notification event arrives.
- Status transitions: Test the full lifecycle:
idle → working (PreToolUse) → waiting (Notification) → working (PostToolUse) → idle (Stop). Ensure no status is skipped or stuck.
- Edge cases:
- Notification event with empty
message field
- Notification event with missing
session_id
- Notification event for a session that doesn't exist yet (should auto-create per current logic at
hooks.py:100-103)
- Rapid successive Notification events for the same session
- Notification followed immediately by a Stop event (race condition)
2. JSONL-based fallback detection
If the Notification hook proves unreliable across Claude Code versions, implement a fallback that detects permission-waiting state from the JSONL conversation file.
Approach:
- In
server/watcher.py, when processing new JSONL lines, look for patterns that indicate Claude is waiting for permission (e.g., tool use entries that haven't been followed by a tool_result within a timeout window).
- If detected and the session status is not already
"waiting", update the status as a fallback.
- This adds latency (up to the 1-second debounce in
watcher.py:19 plus file system notification delay) compared to the hook approach, but provides a safety net.
3. Staleness detection for "waiting" status
Currently server/stale.py checks for stale sessions, but there should be specific handling for sessions stuck in "waiting" status:
- If a session has been in
"waiting" for an unusually long time (e.g., >10 minutes), surface this prominently in the dashboard — the operator may have missed it.
- Consider adding a notification sound or browser notification for
"waiting" transitions.
Acceptance criteria
Context
The hook event flow is: Claude Code fires hook → POST /api/hooks → process_hook_event() in hooks.py → DB update → broadcast_session_update() → WebSocket to all dashboard clients. The _notify_update callback is wired in main.py:23 via set_update_callback(broadcast_session_update).
Problem
The "waiting" (yellow) status in the dashboard is the most important signal in the cockpit — it tells the operator that a Claude Code session is blocked waiting for permission approval. This status is driven entirely by the
Notificationhook event type firing from Claude Code.Current implementation (
server/hooks.py:151-156):The hook is received via
POST /api/hooks(server/routes/api.py:73-80) and the status update is broadcast to all dashboard clients via WebSocket (server/routes/ws.py:19-33).The concern: The
Notificationevent type has been inconsistent across Claude Code versions. If the hook doesn't fire, the session appears "working" when it's actually blocked — the operator has no idea a session needs attention. This is a silent failure with high impact.What needs to happen
1. Comprehensive test coverage for the Notification hook pipeline
Write end-to-end tests that verify the full path:
Notificationhook payload hittingPOST /api/hooksand verify the session status transitions to"waiting"in the database.session_updatemessage withstatus: "waiting"and the correcttask_descriptionwhen a Notification event arrives.idle→working(PreToolUse) →waiting(Notification) →working(PostToolUse) →idle(Stop). Ensure no status is skipped or stuck.messagefieldsession_idhooks.py:100-103)2. JSONL-based fallback detection
If the Notification hook proves unreliable across Claude Code versions, implement a fallback that detects permission-waiting state from the JSONL conversation file.
Approach:
server/watcher.py, when processing new JSONL lines, look for patterns that indicate Claude is waiting for permission (e.g., tool use entries that haven't been followed by atool_resultwithin a timeout window)."waiting", update the status as a fallback.watcher.py:19plus file system notification delay) compared to the hook approach, but provides a safety net.3. Staleness detection for "waiting" status
Currently
server/stale.pychecks for stale sessions, but there should be specific handling for sessions stuck in"waiting"status:"waiting"for an unusually long time (e.g., >10 minutes), surface this prominently in the dashboard — the operator may have missed it."waiting"transitions.Acceptance criteria
Context
The hook event flow is: Claude Code fires hook →
POST /api/hooks→process_hook_event()inhooks.py→ DB update →broadcast_session_update()→ WebSocket to all dashboard clients. The_notify_updatecallback is wired inmain.py:23viaset_update_callback(broadcast_session_update).