Symptom
On a long Claude session the UI sometimes keeps showing Working / Thinking, or goes quiet, and the user cannot tell whether the turn is still making progress. Stop exists, but there is no clear “this is wedged” signal, so people wait.
This is a real failure mode in daily use, not a hypothetical.
What already exists
Do not reinvent these — read them first and extend them:
- Bridge liveness on
GET /sessions: turn_running, last_output_at, current_tool, turn_elapsed_s, queued_turns, awaiting (devscope_bridge/session_http.py, session_manager.get_session_state).
- Stale-turn sweeper / hard-cap interrupt in
devscope_bridge/session_lifecycle.py and session_reader.py.
- Panel stuck heuristics:
isTurnStuck (90s with no lastOutputAt) in extension/src/sidepanel/utils/workingState.ts; FORCE_TURN_IDLE when the bridge says idle but the UI still spins (useSessionManager.ts).
/medic and Stream debug panel.
Likely gaps (investigate, then fix)
- Missing
last_output_at — isTurnStuck returns false if that field is null, so a hung spawn never looks stuck.
- CLI alive but silent — process exists,
turn_running=true, no stdout (tool hang, stdin stall, compact). Sweeper may not fire until the absolute cap.
- UI vs process disagree — WS chunks dropped; spinner or idle is wrong; user has no way to know which is true.
- No user-facing diagnosis — amber strip / medic exist, but a long chat still feels like “maybe it’s thinking.”
Done when
Python-first is fine (session_manager / session_reader / GET /sessions). A small UI change in the strip is OK if the signal has to be visible.
Good first slice: add logging + a failing test around “turn_running=true, no last_output_at / last_output_at frozen”, then the UI rule.
Symptom
On a long Claude session the UI sometimes keeps showing Working / Thinking, or goes quiet, and the user cannot tell whether the turn is still making progress. Stop exists, but there is no clear “this is wedged” signal, so people wait.
This is a real failure mode in daily use, not a hypothetical.
What already exists
Do not reinvent these — read them first and extend them:
GET /sessions:turn_running,last_output_at,current_tool,turn_elapsed_s,queued_turns,awaiting(devscope_bridge/session_http.py,session_manager.get_session_state).devscope_bridge/session_lifecycle.pyandsession_reader.py.isTurnStuck(90s with nolastOutputAt) inextension/src/sidepanel/utils/workingState.ts;FORCE_TURN_IDLEwhen the bridge says idle but the UI still spins (useSessionManager.ts)./medicand Stream debug panel.Likely gaps (investigate, then fix)
last_output_at—isTurnStuckreturns false if that field is null, so a hung spawn never looks stuck.turn_running=true, no stdout (tool hang, stdin stall, compact). Sweeper may not fire until the absolute cap.Done when
Python-first is fine (
session_manager/session_reader/GET /sessions). A small UI change in the strip is OK if the signal has to be visible.Good first slice: add logging + a failing test around “turn_running=true, no last_output_at / last_output_at frozen”, then the UI rule.