Skip to content

fix(chat): reliable self-terminating turns — kill the infinite thinking spinner (v0.192.1)#322

Merged
vikasprogrammer merged 1 commit into
mainfrom
feat/chat-harden
Jul 14, 2026
Merged

fix(chat): reliable self-terminating turns — kill the infinite thinking spinner (v0.192.1)#322
vikasprogrammer merged 1 commit into
mainfrom
feat/chat-harden

Conversation

@vikasprogrammer

Copy link
Copy Markdown
Owner

The bug (live repro on instapods)

A chat reply landed as a chat.delivered (45 chars injected into a live pane) but Claude produced no turn, the pane died, and the row stayed running — so the UI showed "thinking…" forever. Root cause: v1 drove replies by typing into a warm resident TUI pane, which is racy (pane reaped between turns; injected keystrokes not reliably triggering a turn) and gives the UI no honest liveness signal.

The fix (minimal harden — keeps the transcript-driven substrate)

Every chat turn is now a clean, self-terminating governed run instead of a warm-pane keystroke:

  • New TerminalManager.chatSend — a headless in-place resume of the same transcript, seeded with the message as the launch prompt (claude --resume <id> --dangerously-skip-permissions "<msg>"). The message is the prompt (reliably starts a turn), and the run tears down at turn-end so alive reflects reality.
  • chat/start is now a headless one-shot (no lingering warm pane to race).
  • Busy handling: a reply sent while the prior turn is still generating returns 409 busy; the UI keeps the draft and asks the user to resend.
  • UI liveness: "thinking…" is driven by real pane liveness (session.alive) and is bounded — a genuinely stalled turn shows a Resend instead of spinning.

Trade: a cold start per turn (no warm latency). Removed properly by v2.

Governance is unchanged — the PreToolUse gate hook still mediates every effect under --dangerously-skip-permissions.

v2 plan (bundled)

docs/sdk-chat-runtime-plan.md — drive the chat surface via the Claude Agent SDK (query() + includePartialMessages + canUseTool → the existing gateway.ts) for token streaming and a first-class approval hook, running beside the CLI/tmux runtime. Not started; greenlight before building.

Verified

  • npm run typecheck + server build + cd web && npm run build
  • npm run test:governance → 68/68 ✓
  • In-process test of chatSend branching: unknown→error, empty→error, dead-session→sent with the row flipped to running/headless=1/resident=0 and the new task ✓
  • Live smoke on instapods to follow after merge (start a chat, reply, confirm the agent responds + no infinite spinner).

🤖 Generated with Claude Code

…ing…" (v0.192.1)

v1 delivered chat replies by typing into a warm resident TUI pane; when the pane
was reaped or the keystrokes didn't trigger a turn, the message was lost and the
UI spun forever (row still 'running'). Replies now go through chatSend: each turn
is a clean headless resume seeded with the message as the prompt (reliable, not
keystroke injection), self-terminating so `alive` is honest. UI "thinking…" is
bounded by real liveness, with Resend on a stalled turn and a busy signal when a
prior turn is still generating. chat/start is likewise a headless one-shot.

Also adds docs/sdk-chat-runtime-plan.md — the v2 that drives chat via the Claude
Agent SDK (canUseTool → the existing gateway) for streaming + a real approval hook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant