Skip to content

feat(ux): long-turn visibility + default-on stream hang safety net - #1758

Merged
kevincodex1 merged 1 commit into
Twigpine:mainfrom
kevincodex1:feat/long-turn-visibility-hang-safety
Jun 23, 2026
Merged

kevincodex1 merged 1 commit into
Twigpine:mainfrom
kevincodex1:feat/long-turn-visibility-hang-safety

Conversation

@kevincodex1

@kevincodex1 kevincodex1 commented Jun 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Long-running turns looked frozen — a multi-minute turn (e.g. a long subagent or
typecheck) showed a bare spinner, prompting "are you still working?" — and a
silently dropped network stream could hang for the full 5-minute QueryGuard idle
timeout. Three focused changes close those gaps.

  • Elapsed timer at 5s, not 30s (SpinnerAnimationRow.tsx): split the single
    wantsTimerAndTokens gate into wantsTimer (SHOW_TIMER_AFTER_MS = 5_000) and
    wantsTokens (SHOW_TOKENS_AFTER_MS = 30_000). The timer is wall-clock derived,
    so it keeps ticking during long tool calls where no tokens stream — proof of life.
  • Active tool name in the spinner (REPL.tsx): new activeToolSpinnerSuffix
    memo derives the first in-progress tool from the last assistant message ∩
    inProgressToolUseIDs and surfaces it via the existing spinnerSuffix channel
    (stop-hook progress still takes precedence). A long tool now reads
    (↓ Bash · 1m 20s) instead of a bare spinner. No new prop plumbing.
  • Stream idle watchdog on by default (claude.ts): the watchdog already
    existed but was gated behind CLAUDE_ENABLE_STREAM_WATCHDOG. Flipped to default-on
    (matching the always-on readWithTimeout in the OpenAI/Codex shims). A dropped
    Anthropic-family stream now aborts and falls back to a non-streaming retry within
    STREAM_IDLE_TIMEOUT_MS instead of hanging to 5 minutes.

Impact

  • No new dependencies; +32/−8 across 3 files.
  • Behaviour change: the Anthropic-family stream watchdog is now active for all
    users. Default timeout kept conservative at 90s (covers Anthropic/Bedrock/
    Vertex, which send SSE pings). On fire it throws → existing non-streaming retry
    fallback. Opt out with CLAUDE_DISABLE_STREAM_WATCHDOG=1; tune with
    CLAUDE_STREAM_IDLE_TIMEOUT_MS.
  • Spinner changes are display-only.

Testing

  • bun run typecheck (tsc --noEmit) — clean
  • bun test src/__tests__/bugfixes.test.ts — 31 pass (incl. stream-idle assertions)
  • bun test src/services/api — 835 pass
  • bun run smoke — bundle builds + --version runs
  • Manual TUI check of the new spinner suffix / 5s timer (not yet captured)

Notes

Follow-ups deliberately deferred:

  • Surface the watchdog's 45s warning as a UI "reconnecting…" line (needs event
    plumbing from the API layer to the REPL). Token-streaming stalls already show a
    red spinner at 3s via useStalledAnimation, but it's suppressed while tools run.
  • Un-blank streaming text under reduced-motion (REPL.tsx showStreamingText).
  • Optionally tighten the 90s watchdog default toward ~30s.

Summary by CodeRabbit

Release Notes

  • New Features

    • Tool names now displayed in loading spinners when tools execute, with counts shown for multiple simultaneous tool operations.
    • Timer and token count display behavior improved with separate visibility logic for better rendering in narrow terminal layouts.
  • Chores

    • Stream watchdog functionality enabled by default; can be disabled via environment configuration.

Long-running turns looked frozen ("are you still working?") and a dropped
network stream could hang for the full 5-minute QueryGuard idle timeout.
Three focused changes:

- Spinner: show the elapsed-time counter at 5s instead of 30s (split the
  timer gate from the token-count gate). The timer is wall-clock derived,
  so it keeps ticking during long tool calls — proof of life even when no
  tokens stream.
- Spinner: surface the currently-executing tool name in the status line
  (reusing the spinnerSuffix channel; stop-hook progress still wins). A
  long subagent/typecheck now reads "(↓ Bash · 1m 20s)" instead of a bare
  spinner.
- claude.ts: enable the stream idle-timeout watchdog by default, matching
  the always-on read-timeout already used by the OpenAI/Codex shims. A
  silently dropped Anthropic-family stream now aborts and falls back to a
  non-streaming retry within STREAM_IDLE_TIMEOUT_MS (90s) instead of
  hanging to 5 minutes. Opt out with CLAUDE_DISABLE_STREAM_WATCHDOG=1.

Testing: tsc --noEmit clean; bugfixes.test.ts (31) and src/services/api
(835) pass; bun run smoke builds + runs.

Co-Authored-By: OpenClaude <openclaude@gitlawb.com>
@coderabbitai

coderabbitai Bot commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 265d4381-c468-464f-9739-5d603acffbd6

📥 Commits

Reviewing files that changed from the base of the PR and between dd4c4ab and cc1eb77.

📒 Files selected for processing (3)
  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/screens/REPL.tsx
  • src/services/api/claude.ts
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (11)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript with strict mode and ESM imports

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + Ink for terminal UI implementations

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
**/*.{ts,tsx,js,jsx,py,json,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow the existing code style in the touched files

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
**/*.{ts,tsx,js,jsx,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep comments useful and concise

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Follow TypeScript strict mode and type safety practices by running typecheck before submitting

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
**

⚙️ CodeRabbit configuration file

**: # AGENTS.md - AI Agent Coding Guide

This guide is for AI coding agents working in the OpenClaude repository. Read it before changing code, and also follow CONTRIBUTING.md for contributor policy, PR expectations, review follow-up, and project scope.

Project Snapshot

OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI.

The installed CLI runs on Node.js >=22.0.0. Bun is used for source builds, scripts, dependency management, and tests.

Work Style

  • Keep changes focused on one problem.
  • Prefer existing patterns in the file or nearby module.
  • Avoid unrelated formatting, renames, dependency changes, or broad rewrites.
  • Add or update tests when behavior changes.
  • Update docs when setup, commands, provider behavior, or user-facing behavior changes.
  • For new features, larger refactors, dependencies, or runtime changes, follow the issue-first guidance in CONTRIBUTING.md.

Stack And Conventions

  • TypeScript with strict mode and ESM imports.
  • React + Ink for terminal UI.
  • Bun lockfile and Bun scripts for development workflows.
  • Node runtime for the built CLI.
  • Python exists for legacy/local-provider helper code. Do not add new Python code or expand Python-based features unless a maintainer explicitly approves that direction.

Common libraries and patterns:

  • chalk for terminal color.
  • commander for CLI argument parsing.
  • execa for child processes.
  • Existing service, provider, settings, permission, and UI patterns over new abstractions.

Repository Map

  • src/commands/ - slash and CLI command implementations.
  • src/components/ - React/Ink UI components.
  • src/services/ - API, MCP, OAuth, wiki, voice, and other service integrations.
  • src/tools/ - tool implementations.
  • src/utils/ - shared utilities.
  • `src/integration...

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the OpenClaude maintainer review rubric from AGENTS.md. Review the current diff, not stale discussion context. Separate real blockers from suggestions. Do not request changes for vague style churn. Treat approval as merge-ready from CodeRabbit's side, pending required human review and GitHub Checks. If checks are failing or unavailable, say so clearly instead of implying the PR is fully ready.

Files:

  • src/components/Spinner/SpinnerAnimationRow.tsx
  • src/services/api/claude.ts
  • src/screens/REPL.tsx
{src/commands/**/*.ts,src/services/**/*.ts,src/entrypoints/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use chalk for terminal color in CLI code

Files:

  • src/services/api/claude.ts
{src/services/**/*.ts,src/utils/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use execa for child processes

Files:

  • src/services/api/claude.ts
{src/integrations/**/*.ts,src/services/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Test the exact provider/model path you changed when possible for provider modifications

Files:

  • src/services/api/claude.ts
{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}

⚙️ CodeRabbit configuration file

{src/services/api/**,src/integrations/**,src/utils/model/**,src/utils/provider*.ts,src/commands/provider/**}: Review provider routing, model selection, env precedence, auth/token handling, OpenAI-compatible shims, retries, proxy behavior, and outbound HTTP behavior with high scrutiny. Block on silent default changes, hidden fallback expansion, credential reuse mistakes, hardcoded provider assumptions, or new network reach that is not intentional and documented.

Files:

  • src/services/api/claude.ts
🔇 Additional comments (3)
src/components/Spinner/SpinnerAnimationRow.tsx (1)

20-23: LGTM!

Also applies to: 190-191, 202-204

src/screens/REPL.tsx (1)

1756-1768: LGTM!

Also applies to: 4778-4778

src/services/api/claude.ts (1)

73-73: LGTM!

Also applies to: 1953-1961


📝 Walkthrough

Walkthrough

Three independent changes: SpinnerAnimationRow gains separate 5s threshold constants for showing the elapsed-time counter versus token count; REPL derives an active-tool-name suffix displayed in the spinner when no stop-hook status is present; claude.ts inverts the streaming idle watchdog to enabled-by-default with explicit opt-out env vars.

Changes

Spinner display improvements

Layer / File(s) Summary
Independent timer vs. token eligibility
src/components/Spinner/SpinnerAnimationRow.tsx
Adds SHOW_TIMER_AFTER_MS (5s) constant and replaces the single wantsTimerAndTokens boolean with separate wantsTimer and wantsTokens booleans; width-gating for each is computed independently so the two counters can appear at different elapsed-time thresholds.
Active-tool name suffix in REPL spinner
src/screens/REPL.tsx
Adds a useMemo that reads in-progress tool_use blocks from the latest assistant message and produces a "toolName +N" suffix string; passes it as the ?? fallback on SpinnerWithVerb's spinnerSuffix prop.

Stream watchdog default change

Layer / File(s) Summary
Watchdog enabled by default, opt-out via env vars
src/services/api/claude.ts
Imports isEnvDefinedFalsy; rewrites the watchdog guard so it is on by default and disabled only when CLAUDE_DISABLE_STREAM_WATCHDOG is truthy or CLAUDE_ENABLE_STREAM_WATCHDOG is defined falsy.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Gitlawb/openclaude#1503: Modifies Spinner.tsx and SpinnerWithVerb behavior tied to apiMetricsRef, overlapping with the spinner display path changed here.

Suggested reviewers

  • jatmn
🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed Title concisely captures the three key changes (elapsed timer, tool visibility, stream watchdog) and matches the actual diff scope.
Description check ✅ Passed Description is comprehensive with clear Summary, Impact, and Testing sections; all required sections are present and well-filled with technical detail and rationale.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Risk Surface Disclosed ✅ Passed PR touches outbound network behavior (stream idle watchdog now enabled by default). Risk surface properly disclosed: clear explanation in PR Impact section, escape hatches documented (CLAUDE_DISABL...
No Hidden Policy Change ✅ Passed Stream watchdog default-on is the only policy change; it's explicitly documented in PR title/description and code comments with clear opt-out mechanisms. No hidden policies in telemetry, trust-mode...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@kevincodex1
kevincodex1 requested a review from jatmn June 23, 2026 12:38
@kevincodex1
kevincodex1 merged commit 4dee44a into Twigpine:main Jun 23, 2026
4 checks passed
hotmanxp pushed a commit to hotmanxp/openclaude that referenced this pull request Jun 25, 2026
…wigpine#1758)

Long-running turns looked frozen ("are you still working?") and a dropped
network stream could hang for the full 5-minute QueryGuard idle timeout.
Three focused changes:

- Spinner: show the elapsed-time counter at 5s instead of 30s (split the
  timer gate from the token-count gate). The timer is wall-clock derived,
  so it keeps ticking during long tool calls — proof of life even when no
  tokens stream.
- Spinner: surface the currently-executing tool name in the status line
  (reusing the spinnerSuffix channel; stop-hook progress still wins). A
  long subagent/typecheck now reads "(↓ Bash · 1m 20s)" instead of a bare
  spinner.
- claude.ts: enable the stream idle-timeout watchdog by default, matching
  the always-on read-timeout already used by the OpenAI/Codex shims. A
  silently dropped Anthropic-family stream now aborts and falls back to a
  non-streaming retry within STREAM_IDLE_TIMEOUT_MS (90s) instead of
  hanging to 5 minutes. Opt out with CLAUDE_DISABLE_STREAM_WATCHDOG=1.

Testing: tsc --noEmit clean; bugfixes.test.ts (31) and src/services/api
(835) pass; bun run smoke builds + runs.

Co-authored-by: OpenClaude <openclaude@gitlawb.com>
(cherry picked from commit 4dee44a)
(cherry picked from commit c742a18bb3dd89120aa11b6d8602feef61b0e9a7)
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