fix: live web-chat updates for terminal-initiated Claude turns - #275
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @centdix's task in 2m 39s —— View job Code review: PR #275 — live web-chat updates for terminal-initiated Claude turns
Overall this is a clean, well-scoped frontend-only fix. The What's correct
Non-blocking considerations
Parity note (per AGENTS.md)This is a web-chat live-update fix with no CLI-facing surface, so the frontend/CLI parity rule doesn't apply here. ✅ No changes requested — the implementation looks correct and the reasoning in the PR description matches the code. The two items above are follow-up suggestions, not blockers. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
webmux | f20fef9 | Commit Preview URL Branch Preview URL |
Jun 19 2026, 11:53 PM |
|
Claude finished @centdix's task in 2m 5s —— View job Code review: PR #275 — live web-chat updates for terminal-initiated Claude turns
This is a clean, correctly-scoped frontend-only fix. I traced the full polling lifecycle and the effect interactions against the surrounding code ( What's correct
Non-blocking considerations
Parity note (per AGENTS.md)Web-chat live-update fix with no CLI-facing surface, so the frontend/CLI parity rule doesn't apply. ✅ Verdict: Implementation is correct and the reasoning in the PR description matches the code. All four items above are follow-ups, not blockers. |
Summary
The Claude web chat (
MobileChatSurface.svelte) only live-streams turns sent from the web chat. Those run as a backend-ownedclaude -ptracked byclaudeConversationStreamService, which broadcasts WS stream events the frontend subscribes to.The initial worktree-creation prompt (and anything typed in the terminal pane) instead launches an interactive
claude -- <prompt>in the tmux pane, so the stream service has no run for it. On attach, the conversation snapshot reportsrunning: false,syncConversationStreamopens nothing, and (since #262 removed the history-polling fallback on attach) the chat shows only the static attach-time snapshot and never updates live.This PR restores message-level live updates for terminal-owned Claude turns by reusing the existing refresh-polling machinery — no second polling system, no backend changes.
Root cause
conversation.runningstaysfalse.syncConversationStreamonly opens a stream whenrunning === true(or on a forced send), so a fresh attach to a busy terminal turn subscribes to nothing.Fix (frontend-only)
$effectthat, while the worktree agent is busy (worktree.agent === "working") and the conversation is a Claude conversation that is not backend-streaming (provider === "claudeCode"andrunning !== true), starts the existingstartRefreshPollingloop sofetchWorktreeConversationHistoryis polled and the terminal claude's flushed messages appear live."working"), so it never runs forever on an idle worktree.startRefreshPollinggains astopWhenIdleflag; for these terminal-owned pollsupdateRefreshPollingStateskips the message-progress settle heuristic (which can stop early mid-turn) and instead relies on the agent-busy signal to gate the lifecycle. Send/interrupt-initiated polls are unchanged (stopWhenIdledefaults tofalse).Orthogonal to #274 (keep-stream-open across turns): this change does not touch
syncConversationStream.Changes
frontend/src/lib/MobileChatSurface.sveltestopWhenIdletorefreshPollingStateandstartRefreshPolling.stopWhenIdlepolls inupdateRefreshPollingState.$effectthat starts/stops history polling for terminal-owned Claude turns.frontend/src/lib/MobileChatSurface.test.tsTest plan
waitFortimes out) and passes after.bun run testinfrontend— 118 passed (15 files), including existing stream/keep-open and terminal-routed-send tests.bun run checkinfrontend— 0 errors, 0 warnings.Generated with Claude Code