BUG-HUNT: [concurrency] on_input_submitted calls subprocess.run() synchronously on the Textual event loop — UI freezes for up to 30 seconds on every shell command #6603

Open
opened 2026-04-09 22:06:18 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: Concurrency — Blocking subprocess.run() on Textual async event loop

Severity Assessment

  • Impact: The entire TUI freezes (keyboard, mouse, redraws all stop) for up to the 30-second shell timeout every time the user runs a ! shell command. During this window the app is completely unresponsive.
  • Likelihood: High — triggered every time the user enters shell mode (any !<cmd> input).
  • Priority: High

Location

  • File: src/cleveragents/tui/app.py
  • Function/Class: _TextualCleverAgentsTuiApp.on_input_submitted
  • Lines: ~168–199
  • Also: src/cleveragents/tui/input/shell_exec.pyrun_shell_command() line 70

Description

on_input_submitted is a synchronous Textual event handler. Inside it, when shell mode is detected, the router calls run_shell_command(), which invokes subprocess.run(... , timeout=timeout_seconds) (default 30 s):

# app.py — synchronous handler, no async/await
def on_input_submitted(self, event: InputSubmittedEvent) -> None:
    ...
    result = mode_router.process(text)   # calls run_shell_command() inside
    ...
# shell_exec.py
proc = subprocess.run(
    command,
    shell=True,
    text=True,
    capture_output=True,
    timeout=timeout_seconds,   # default 30 seconds
)

Textual runs on an asyncio event loop. A synchronous subprocess.run() call on the event loop thread blocks the entire loop for the full duration of the subprocess. During this time:

  • No keyboard events are processed
  • No redraws occur
  • No async Textual callbacks execute
  • The terminal appears completely frozen to the user

Expected Behavior

Shell commands should run in a worker thread (or via asyncio.to_thread) so the Textual event loop remains responsive. The result should be dispatched back to the main thread via Textual's call_from_thread() or a Worker.

Actual Behavior

The event loop is blocked for the duration of every shell command, which can be up to 30 seconds for the default timeout. Commands like sleep 25 will freeze the TUI for 25 seconds.

Suggested Fix

Convert on_input_submitted to async and offload shell execution to a Textual worker:

async def on_input_submitted(self, event: InputSubmittedEvent) -> None:
    ...
    if mode == InputMode.SHELL:
        # Run blocking subprocess in a thread pool
        shell_result = await asyncio.to_thread(
            run_shell_command,
            command,
            confirm_dangerous=self._shell_confirm,
            timeout_seconds=self._shell_timeout_seconds,
        )
        conversation.update(f"$ {shell_result.command}\n{output}")
        return

Alternatively, use Textual's run_worker() with exclusive=True for shell commands.

Category

concurrency

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: Concurrency — Blocking subprocess.run() on Textual async event loop ### Severity Assessment - **Impact**: The entire TUI freezes (keyboard, mouse, redraws all stop) for up to the 30-second shell timeout every time the user runs a `!` shell command. During this window the app is completely unresponsive. - **Likelihood**: High — triggered every time the user enters shell mode (any `!<cmd>` input). - **Priority**: High ### Location - **File**: `src/cleveragents/tui/app.py` - **Function/Class**: `_TextualCleverAgentsTuiApp.on_input_submitted` - **Lines**: ~168–199 - **Also**: `src/cleveragents/tui/input/shell_exec.py` — `run_shell_command()` line 70 ### Description `on_input_submitted` is a **synchronous** Textual event handler. Inside it, when shell mode is detected, the router calls `run_shell_command()`, which invokes `subprocess.run(... , timeout=timeout_seconds)` (default 30 s): ```python # app.py — synchronous handler, no async/await def on_input_submitted(self, event: InputSubmittedEvent) -> None: ... result = mode_router.process(text) # calls run_shell_command() inside ... ``` ```python # shell_exec.py proc = subprocess.run( command, shell=True, text=True, capture_output=True, timeout=timeout_seconds, # default 30 seconds ) ``` Textual runs on an asyncio event loop. A synchronous `subprocess.run()` call on the event loop thread **blocks the entire loop** for the full duration of the subprocess. During this time: - No keyboard events are processed - No redraws occur - No async Textual callbacks execute - The terminal appears completely frozen to the user ### Expected Behavior Shell commands should run in a worker thread (or via `asyncio.to_thread`) so the Textual event loop remains responsive. The result should be dispatched back to the main thread via Textual's `call_from_thread()` or a `Worker`. ### Actual Behavior The event loop is blocked for the duration of every shell command, which can be up to 30 seconds for the default timeout. Commands like `sleep 25` will freeze the TUI for 25 seconds. ### Suggested Fix Convert `on_input_submitted` to `async` and offload shell execution to a Textual worker: ```python async def on_input_submitted(self, event: InputSubmittedEvent) -> None: ... if mode == InputMode.SHELL: # Run blocking subprocess in a thread pool shell_result = await asyncio.to_thread( run_shell_command, command, confirm_dangerous=self._shell_confirm, timeout_seconds=self._shell_timeout_seconds, ) conversation.update(f"$ {shell_result.command}\n{output}") return ``` Alternatively, use Textual's `run_worker()` with `exclusive=True` for shell commands. ### Category concurrency ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:25:27 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#6603
No description provided.