Skip to content

fix: route sudo bash_exec through PTY#135

Merged
F16shen merged 1 commit into
AI-Shell-Team:mainfrom
F16shen:fix/bash-exec-sudo-pty-route
Apr 24, 2026
Merged

fix: route sudo bash_exec through PTY#135
F16shen merged 1 commit into
AI-Shell-Team:mainfrom
F16shen:fix/bash-exec-sudo-pty-route

Conversation

@F16shen
Copy link
Copy Markdown
Collaborator

@F16shen F16shen commented Apr 24, 2026

概述

  • 问题: bash_exec 在共享 PTY 路径下执行 sudo/su 一类命令时,会停在密码提示处但无法接收用户输入,导致工具调用卡住。
  • 改动: 对 sudo/su 增加最小启发式分流,命中时绕过共享 PTYManager,改走 UnifiedBashExecutor.execute(use_pty=True);同时修复旧 PTY 执行器的 controlling terminal 建立逻辑,并补充针对分流与 PTY 启动的测试。
  • 关联 Issue: #

改动类型

  • Bug 修复
  • 新功能
  • 重构
  • 文档
  • 其他

涉及范围

  • 核心 Shell / PTY
  • AI 代理 / LLM
  • 技能 / 工具
  • 安全模块
  • 配置系统
  • CLI / 界面
  • 打包 / 安装
  • CI/CD
  • 文档

用户可见变更

  • bash_exec 遇到 sudo/su 时不再卡死在密码提示处,用户输入会被转发到命令。
  • 密码失败或命令结束后,aish 提示符能够正常恢复,后续 shell 命令和普通 bash_exec 调用仍可继续使用。

兼容性

  • 向后兼容? 是
  • 配置变更? 否

测试验证

  • pytest tests/tools/test_bash_executor.py tests/tools/test_bash_output_offload.py -q
  • 真实场景验证 1: 在交互式 aish 会话中让 AI 使用 bash_exec 执行 sudo -k whoami,确认出现密码提示并可消费错误密码输入,命令结束后提示符恢复。
  • 真实场景验证 2: 在同一会话中完成上述 sudo 失败流程后,再执行 echo shell-alivebash_exec("echo tool-ok"),确认 shell 和工具链均恢复正常。
  • AI-assisted: 本 PR 由 Copilot 协助完成;已人工阅读改动并完成上述验证。

检查清单

  • 代码风格符合项目规范
  • 已添加必要的测试
  • 文档已更新(如需要)

Summary by CodeRabbit

  • New Features

    • Added intelligent detection and special routing for interactive bash commands (sudo/su-prefixed) for improved execution handling.
    • Enhanced cancellation support for interactive command scenarios.
  • Bug Fixes

    • Improved terminal configuration robustness with exception-safe error handling and recovery.
  • Tests

    • Added comprehensive test coverage for PTY execution mode and interactive command detection heuristics.

@github-actions
Copy link
Copy Markdown
Contributor

Thanks for the pull request. A maintainer will review it when available.

Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review.

Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

This PR enhances PTY-based command execution by replacing file descriptor re-opening with ioctl(TIOCSCTTY) for controlling terminal setup, adds exception-safe termios configuration, introduces interactive command detection routing sudo/su commands to a dedicated PTY executor, and includes comprehensive test coverage for both PTY functionality and interactive command handling.

Changes

Cohort / File(s) Summary
PTY Executor Setup
src/aish/tools/bash_executor.py
Modified PTY child process initialization to use fcntl.ioctl(TIOCSCTTY) instead of re-closing/re-opening stdin/stdout/stderr against tty. Made termios configuration and restoration exception-safe by clearing old_settings on failure and ignoring restore errors. Moved fcntl import to module scope.
Interactive Command Routing
src/aish/tools/code_exec.py
Added _get_cancel_event() method to adapt cancellation tokens to threading.Event. Implemented interactive command detection to route sudo/su-prefixed commands to dedicated PTY executor (use_pty=True). Suppressed stdout/stderr processing for interactive executor path while preserving behavior for other routes.
PTY Execution Tests
tests/tools/test_bash_executor.py
Added test case verifying PTY execution mode with mocked pty.openpty, termios.tcgetattr/tcsetattr, and fcntl.fcntl/ioctl. Asserts child process wired to PTY slave and validates ioctl(TIOCSCTTY) call for controlling terminal setup.
Interactive Command Detection Tests
tests/tools/test_bash_output_offload.py
Added tests for _needs_interactive_bash heuristics covering sudo/su patterns and command chaining. Verified that interactive commands bypass shared PTY and execute with use_pty=True while suppressing interactive-related output.

Sequence Diagram

sequenceDiagram
    actor User
    participant BashTool
    participant InteractiveDetector
    participant SharedPTY
    participant DedicatedPTY
    participant Subprocess

    User->>BashTool: execute("sudo whoami")
    BashTool->>InteractiveDetector: _needs_interactive_bash("sudo whoami")
    InteractiveDetector-->>BashTool: true
    BashTool->>DedicatedPTY: execute(cmd, use_pty=True)
    DedicatedPTY->>Subprocess: Popen with PTY slave
    Subprocess->>Subprocess: ioctl(TIOCSCTTY) - set controlling terminal
    Subprocess-->>DedicatedPTY: result (with stdin interactive)
    DedicatedPTY-->>BashTool: output
    BashTool-->>User: result (no stdout collapse)

    User->>BashTool: execute("echo test")
    BashTool->>InteractiveDetector: _needs_interactive_bash("echo test")
    InteractiveDetector-->>BashTool: false
    BashTool->>SharedPTY: execute(cmd)
    SharedPTY->>Subprocess: Popen (standard)
    Subprocess-->>SharedPTY: result
    SharedPTY-->>BashTool: output
    BashTool-->>User: result (with stdout collapse)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

tests, experienced-contributor

Poem

🐰 A PTY setup, now sleek and refined,
No re-opens—just ioctl's design,
Interactive commands find their way,
Through dedicated paths, come what may,
With exception-safe hands, we'll catch each fall! 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
Title check ✅ Passed The title directly addresses the main change: routing sudo bash_exec through PTY to fix hangs at password prompts, which is the core problem and solution in this changeset.

✏️ 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 and usage tips.

@F16shen F16shen changed the title fix: route sudo bash_exec through PTY [AI-assisted] fix: route sudo bash_exec through PTY Apr 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/aish/tools/bash_executor.py (1)

290-294: Defensive check is unreachable but harmless.

process.wait(timeout=0.1) either returns the exit code or raises subprocess.TimeoutExpired—it never returns None. The None-check on line 292-293 is dead code but doesn't cause harm. Consider removing it for clarity, or wrapping in a try/except for TimeoutExpired if you want to handle the timeout case explicitly.

♻️ Optional: Handle TimeoutExpired explicitly
             # Wait for process to end
-            returncode = process.wait(timeout=0.1)
-            if returncode is None:
-                returncode = 0
+            try:
+                returncode = process.wait(timeout=0.1)
+            except subprocess.TimeoutExpired:
+                returncode = 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aish/tools/bash_executor.py` around lines 290 - 294, The None-check after
calling process.wait(timeout=0.1) is unreachable because subprocess.wait either
returns an integer exit code or raises subprocess.TimeoutExpired; update the
logic around the process.wait call in bash_executor.py to explicitly handle the
timeout instead of checking for None: wrap process.wait(timeout=0.1) in a
try/except catching subprocess.TimeoutExpired and set a sensible returncode
(e.g., None, -1, or leave running) or remove the dead None-check entirely; refer
to the process.wait(timeout=0.1) invocation and the surrounding returncode
variable to locate and adjust the code.
src/aish/tools/code_exec.py (1)

27-29: Heuristic is minimal but functional; consider documenting edge cases.

The heuristic correctly identifies common sudo/su patterns. Note that it won't catch:

  • su without arguments (user might just type su)
  • Alternative privilege escalation tools like doas or pkexec

Since the PR explicitly describes this as a "minimal heuristic," this is acceptable. Consider adding a comment noting these limitations for future maintainers.

📝 Optional: Add docstring noting limitations
 def _needs_interactive_bash(command: str) -> bool:
+    """Minimal heuristic to detect commands requiring interactive PTY.
+    
+    Catches common sudo/su patterns. Does not cover all privilege
+    escalation tools (e.g., doas, pkexec) or bare 'su' without args.
+    """
     lower = command.lower()
     return "sudo" in lower or " su " in lower or lower.startswith("su ")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aish/tools/code_exec.py` around lines 27 - 29, The
_needs_interactive_bash function uses a minimal heuristic that only checks for
"sudo" and common "su" patterns; add a short docstring or inline comment on the
_needs_interactive_bash function explaining its limitations (it won't detect
bare "su" without args, or other escalation tools like doas or pkexec) and note
that this is intentionally minimal so future maintainers know why more
exhaustive checks were not implemented and where to extend detection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/aish/tools/bash_executor.py`:
- Around line 290-294: The None-check after calling process.wait(timeout=0.1) is
unreachable because subprocess.wait either returns an integer exit code or
raises subprocess.TimeoutExpired; update the logic around the process.wait call
in bash_executor.py to explicitly handle the timeout instead of checking for
None: wrap process.wait(timeout=0.1) in a try/except catching
subprocess.TimeoutExpired and set a sensible returncode (e.g., None, -1, or
leave running) or remove the dead None-check entirely; refer to the
process.wait(timeout=0.1) invocation and the surrounding returncode variable to
locate and adjust the code.

In `@src/aish/tools/code_exec.py`:
- Around line 27-29: The _needs_interactive_bash function uses a minimal
heuristic that only checks for "sudo" and common "su" patterns; add a short
docstring or inline comment on the _needs_interactive_bash function explaining
its limitations (it won't detect bare "su" without args, or other escalation
tools like doas or pkexec) and note that this is intentionally minimal so future
maintainers know why more exhaustive checks were not implemented and where to
extend detection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 62eec3e4-9e7c-4fc8-8a46-dcbaf2ce5730

📥 Commits

Reviewing files that changed from the base of the PR and between 64dc828 and 01eb877.

📒 Files selected for processing (4)
  • src/aish/tools/bash_executor.py
  • src/aish/tools/code_exec.py
  • tests/tools/test_bash_executor.py
  • tests/tools/test_bash_output_offload.py

@F16shen F16shen merged commit 27ca5d4 into AI-Shell-Team:main Apr 24, 2026
13 checks passed
@F16shen F16shen deleted the fix/bash-exec-sudo-pty-route branch April 27, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant