Skip to content

feat(chat): show Running code status during Bash execution#82

Merged
Zhang-Henry merged 2 commits intomainfrom
feat/status-running-code
Mar 27, 2026
Merged

feat(chat): show Running code status during Bash execution#82
Zhang-Henry merged 2 commits intomainfrom
feat/status-running-code

Conversation

@MarkSiqiZhang
Copy link
Copy Markdown
Collaborator

Summary

  • When an agent executes Bash/shell commands, the status now shows "Running code" instead of generic "Thinking"
  • Applies to all 3 providers: Claude, Codex, and Gemini
  • Other tools (Edit, Grep, WebSearch, etc.) continue to show "Thinking"

Changes

  • useChatRealtimeHandlers.ts — Detect Bash/run_shell_command tool_use and update claudeStatus.text to "Running code" for
    Claude/Gemini; detect command_execution for Codex
  • AssistantThinkingIndicator.tsx — Accept statusText prop, default to i18n status.thinking
  • ChatMessagesPane.tsx — Thread statusText prop to indicator
  • ChatInterface.tsx — Pass claudeStatus?.text to ChatMessagesPane
  • i18n — All 3 locales (en, zh-CN, ko) updated with status.thinking and status.runningCode

Test plan

  • Send a message that triggers Bash execution → status bar and thinking indicator show "Running code"
  • Send a normal message (no tool use) → status shows as before
  • Edit/Write/Grep tools → status does not stay "Running code"
  • Works for Claude, Codex, and Gemini

When an agent runs Bash/shell commands, the status indicator and
thinking text now show "Running code" instead of generic "Thinking".
Other tools (Edit, Grep, WebSearch, etc.) still show "Thinking".
Copy link
Copy Markdown
Collaborator

@Zhang-Henry Zhang-Henry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall: Clean, small PR with clear intent. Two functional bugs should be addressed before merge.

Issues

1. [Bug] Hardcoded English string bypasses i18n

useChatRealtimeHandlers.ts lines 179-181 and 901 directly set text: 'Running code' as a literal English string, but the i18n keys status.runningCode are already defined in all 3 locales (chat.json). This means:

  • Chinese users see "思考中..." → "Running code" (mixed languages)
  • Korean users likewise see English

The issue is that useChatRealtimeHandlers is a hook where calling t() directly isn't straightforward. Suggested fix: instead of setting display text in the hook, set a semantic key (e.g., text: 'running_code'), then resolve it via i18n in AssistantThinkingIndicator at render time. Currently status.runningCode is defined in all 3 locale files but never referenced — it's dead code.

2. [Bug] Status text never resets after Bash execution completes

Tracing the full lifecycle:

  • claudeStatus is defined at useChatSessionState.ts:94 as { text, tokens, can_interrupt } | null
  • Set to non-null: when claude-status/gemini-status messages arrive (useChatRealtimeHandlers.ts:1134-1147) — text comes from backend's statusData.message
  • Cleared to null: clearLoadingIndicators() on session complete/error/abort (useChatRealtimeHandlers.ts:392-396)

The PR sets text to 'Running code' on Bash tool_use detection, but nothing resets it back. If Bash finishes and the model continues thinking (e.g., processing results before emitting text), text remains 'Running code' until either the backend pushes a new claude-status event or the session ends.

In practice this may be masked by frequent backend status pushes, but in edge cases (Bash completes → model directly outputs text without a new status push), the indicator stays stuck on "Running code".

Suggestion: reset text when a tool_result arrives or when a new text/thinking content block begins.

3. [Minor] Codex command_execution triggers on all lifecycle stages

The command_execution case (~line 793) handles all lifecycle phases (started/completed/etc.). The PR sets the status at the case entry, meaning even the completed phase triggers "Running code". Should only set on command start.

4. [Nit] Gemini coverage

PR description says "Applies to all 3 providers", but Bash detection is only added to Claude and Codex code paths. Gemini shares the claude-status/gemini-status handler but its tool_use events go through the same path as Claude (shared case), so Bash detection at line 179 should cover Gemini too — worth verifying this is actually the case.

@MarkSiqiZhang
Copy link
Copy Markdown
Collaborator Author

Review Response

All 4 issues addressed. Here's what was done for each:

1. [Bug] i18n bypass — Fixed

Instead of setting display text directly in claudeStatus.text (which mixes frontend override with backend data), introduced a separate statusTextOverride state as a display-layer override. The override is set via i18n.t('chat:status.runningCode') which resolves to the correct locale at call time. claudeStatus is never modified — it always reflects backend data. The i18n keys status.runningCode in all 3 locales are now actively used.

2. [Bug] Status never resets — Fixed

The override is cleared (setStatusTextOverride(null)) in multiple places to cover all providers:

  • handleStructuredAssistantMessage entry — new assistant message = previous tool execution done
  • handleUserToolResults — when tool_result arrives
  • content_block_delta handler (both Claude & Gemini) — streaming text means model is outputting, not executing tools
  • clearLoadingIndicators — session complete/error/abort
  • Codex command_execution with lifecycle === 'completed'

3. [Minor] Codex lifecycle — Fixed

Guarded with if (lifecycle !== 'completed') so only command start sets the override. On completed, the override is cleared.

4. [Nit] Gemini coverage — Verified

Confirmed: Gemini routes through handleStructuredAssistantMessage (same as Claude), so the Bash tool_use detection at the shared path covers Gemini. No separate handling needed.

Architecture note

The key design decision: claudeStatus.text is a backend-owned field. Directly mutating it caused the backend's claude-status/gemini-status pushes to immediately overwrite "Running code". The statusTextOverride pattern keeps them decoupled — the override takes priority at render time via statusTextOverride || claudeStatus?.text, while claudeStatus remains untouched.

@Zhang-Henry Zhang-Henry merged commit e70918b into main Mar 27, 2026
1 check passed
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.

2 participants