Integrated repository, worktree, git stacker, and project manager
0

Configure Feed

Select the types of activity you want to include in your feed.

serve: keep trailing user prompt as working (tributary parity)

The Tier-2 transcript classifier now treats a no-stop_reason tail as
working regardless of record type, matching tributary's jsonl_status.rs.
A cancelled/interrupted turn has no Claude hook to key off, so it stays
"working" until the hook row goes stale; documented inline against
claude-code#9516.

Jordan Isaacs (Jun 22, 2026, 9:04 PM UTC) c2b70496 987ea0a2

+14 -2
+10 -2
src/project_manager/agent/serve/fallback.py
··· 71 71 stop_reason = _stop_reason(last) 72 72 if stop_reason is not None: 73 73 return status_mod.IDLE if stop_reason in _IDLE_STOP_REASONS else status_mod.WORKING 74 - # No stop_reason: a trailing user prompt or a mid-stream assistant 75 - # record both mean the agent has work in flight. 74 + # No stop_reason → working (tributary's `jsonl_status.rs` behaviour). A 75 + # trailing user prompt is treated the same: the turn is assumed in flight. 76 + # 77 + # TODO: this leaves a cancelled/interrupted turn stuck on "working" until 78 + # the hook row goes stale, because Claude fires *no* hook on user interrupt 79 + # — there is no Stop/Notification event to key off. (Confirmed against 80 + # tributary, openui, db-agents: only db-agents catches it, via PTY 81 + # scrollback "Interrupted" parsing, which needs PTY ownership we don't have 82 + # here.) Revisit if Claude Code adds an interrupt hook: 83 + # https://github.com/anthropics/claude-code/issues/9516 76 84 return status_mod.WORKING 77 85 78 86
+4
tests/agent/serve/test_fallback.py
··· 103 103 104 104 105 105 def test_trailing_user_prompt_is_working() -> None: 106 + # A trailing user prompt is assumed to be a turn in flight (tributary's 107 + # `trailing_user_prompt_is_working`). Note: a cancelled turn fires no Claude 108 + # hook, so it stays "working" here until the hook row goes stale — see the 109 + # note in fallback.py (claude-code#9516). 106 110 text = "\n".join( 107 111 [ 108 112 _line(type="assistant", message={"stop_reason": "end_turn"}),