Skip to content

fix: isolate multiline AI bash from shared PTY#190

Open
enderfirst wants to merge 1 commit into
AI-Shell-Team:mainfrom
enderfirst:fix-ai-bash-pty-recovery
Open

fix: isolate multiline AI bash from shared PTY#190
enderfirst wants to merge 1 commit into
AI-Shell-Team:mainfrom
enderfirst:fix-ai-bash-pty-recovery

Conversation

@enderfirst
Copy link
Copy Markdown
Contributor

@enderfirst enderfirst commented May 16, 2026

Summary

  • Problem: AI-generated multiline bash_exec could be routed into the shared interactive PTY. If the backend bash exited unexpectedly, aish treated the backend shell_exiting event as an outer-shell exit and could drop the whole session.
  • Changes: Route multiline bash scripts through the non-shared executor, recover unexpected backend shell_exiting events by restarting the PTY, and preserve explicit exit / quit behavior.
  • Related Issue: N/A - direct bugfix PR for a reproduced product defect.

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Other

Scope

  • Core shell / PTY
  • AI agent / LLM
  • Skills / Tools
  • Security
  • Configuration
  • CLI / Interface
  • Packaging / Installation
  • CI/CD
  • Documentation

User-visible Changes

  • Multiline AI bash_exec no longer contaminates the shared interactive PTY.
  • Unexpected backend bash exits no longer terminate the whole aish session.
  • Explicit exit / quit behavior is unchanged.

Compatibility

  • Backward compatible? Yes
  • Config changes? No

Testing

  • uv run pytest tests/tools/test_bash_output_offload.py tests/shell/runtime/test_shell_pty_core.py -q
  • uv run ruff check src/aish/tools/code_exec.py src/aish/shell/runtime/app.py tests/tools/test_bash_output_offload.py tests/shell/runtime/test_shell_pty_core.py

Checklist

  • Code follows project style
  • Tests added if needed
  • Documentation updated if needed

Logs

aish:~ ➜ ;你所在的网络有个IP地址在对外异常发包。你检查下是哪一台。你直接执行,必要时我给你权限。输出结果给我,并且明确告诉我是否有这样的IP.
╭─────────────────────────────────────────────────────────────────────── 🔐 需要确认:bash_exec ───────────────────────────────────────────────────────────────────────╮
│ 目标: set -euo pipefail                                                                                                                                              │
│ ip -br addr                                                                                                                                                          │
│ printf "\n--- default route ---\n"                                                                                                                                   │
│ ip route show default || true                                                                                                                                        │
│ printf "\n--- neighbors (ARP/NDP) ---\n"                                                                                                                             │
│ ip neigh show nud reachable nud stale nud delay nud probe nud failed 2>/dev/null | head -n 200 || true                                                               │
│ printf "\n--- local listeners (top) ---\n"                                                                                                                           │
│ ss -Htunap 2>/dev/null | head -n 80 || true                                                                                                                          │
│ printf "\n--- recent kernel logs (net-related) ---\n"                                                                                                                │
│ dmesg -T 2>/dev/null | egrep -i 'spoof|martian|rpfilter|conntrack|nf_|iptables|nft|drop|reject|attack|scan' | tail -n 80 || true                                     │
│ printf "\n--- nftables ruleset (if any) ---\n"                                                                                                                       │
│ command -v nft >/dev/null 2>&1 && sudo -n nft list ruleset 2>/dev/null | head -n 120 || true                                                                         │
│ printf "\n--- iptables rules (if any) ---\n"                                                                                                                         │
│ command -v iptables >/dev/null 2>&1 && sudo -n iptables -S 2>/dev/null | head -n 120 || true                                                                         │
│                                                                                                                                                                      │
│ 提示: 未能完成命令风险评估,请确认后执行。                                                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

选项:
  y - 允许
  a - 允许,并不再对该命令询问
  n/c - 取消

你的选择(默认:n):y
🔧 使用工具: bash_exec (set -euo pipefail
ip -br addr
printf "\n--- default route ---\n"
ip route show default || true
printf "\n--- neighbors (ARP/NDP) ---\n"
ip neigh show nud reachable nud stale nud delay nud probe nud failed 2>/dev/null | head -n 200 || true
printf "\n--- local listeners (top) ---\n"
ss -Htunap 2>/dev/null | head -n 80 || true
printf "\n--- recent kernel logs (net-related) ---\n"
dmesg -T 2>/dev/null | egrep -i 'spoof|martian|rpfilter|conntrack|nf_|iptables|nft|drop|reject|attack|scan' | tail -n 80 || true
printf "\n--- nftables ruleset (if any) ---\n"
command -v nft >/dev/null 2>&1 && sudo -n nft list ruleset 2>/dev/null | head -n 120 || true
printf "\n--- iptables rules (if any) ---\n"
command -v iptables >/dev/null 2>&1 && sudo -n iptables -S 2>/dev/null | head -n 120 || true
)
lo               UNKNOWN        127.0.0.1/8 ::1/128
wlp2s0           UP             172.16.4.9/23 fe80::1bd2:db8e:60cf:9b6d/64
enx00e04c680050  UP             1.1.3.9/24 fe80::6ae8:721a:e6c4:f4c2/64

--- default route ---
default via 1.1.3.1 dev enx00e04c680050 proto dhcp src 1.1.3.9 metric 100
default via 172.16.4.1 dev wlp2s0 proto dhcp src 172.16.4.9 metric 600

--- neighbors (ARP/NDP) ---
1.1.3.11 dev enx00e04c680050 lladdr 74:d6:cb:4c:b2:a5 STALE
1.1.3.14 dev enx00e04c680050 lladdr b0:76:1b:26:f4:40 STALE
1.1.3.1 dev enx00e04c680050 lladdr ac:ce:92:6b:18:c0 REACHABLE
1.1.3.240 dev enx00e04c680050 lladdr 74:bf:c0:14:ed:f7 STALE
172.16.5.156 dev wlp2s0 lladdr 9c:58:84:38:2a:a5 REACHABLE
172.16.4.1 dev wlp2s0 lladdr 8c:6d:77:ed:d1:00 STALE

--- local listeners (top) ---
udp UNCONN     0      0                       127.0.0.1:51060         0.0.0.0:*     users:(("sunloginclient_",pid=708533,fd=70))
udp UNCONN     0      0                       127.0.0.1:43139         0.0.0.0:*     users:(("sunloginclient_",pid=708533,fd=18),("sunloginclient_",pid=511302,fd=18))
udp UNCONN     0      0                       127.0.0.1:54800         0.0.0.0:*     users:(("sunloginclient",pid=223960,fd=73))
udp UNCONN     0      0                      1.1.3.9:5657          0.0.0.0:*     users:(("sunloginclient",pid=223960,fd=85))
udp UNCONN     29760  0                         0.0.0.0:14008         0.0.0.0:*     users:(("sunloginclient_",pid=708533,fd=72),("sunloginclient_",pid=511302,fd=72))
tcp CLOSE-WAIT 1      0                      1.1.3.9:50400   【外网IP1】:8080  users:(("aish",pid=713207,fd=18))
tcp ESTAB      0      0                      1.1.3.9:52008   【外网IP1】:8080  users:(("aish",pid=725066,fd=18))
tcp ESTAB      0      0                      1.1.3.9:35510   【外网IP4】:443   users:(("sunloginclient",pid=223960,fd=114))

--- recent kernel logs (net-related) ---

--- nftables ruleset (if any) ---

--- iptables rules (if any) ---
🔧 使用工具: bash_exec (sudo -n true 2>/dev/null && echo HAS_SUDO || echo NO_SUDO
command -v tcpdump >/dev/null 2>&1 && echo HAS_TCPDUMP || echo NO_TCPDUMP
ip -br link)
NO_SUDO
NO_TCPDUMP
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
wlp2s0           UP             74:12:b3:95:cd:ad <BROADCAST,MULTICAST,UP,LOWER_UP>
enx00e04c680050  UP             00:e0:4c:68:00:50 <BROADCAST,MULTICAST,UP,LOWER_UP>
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I can’t confirm any “external abnormal packet-sending IP” from here: no sudo, no tcpdump/conntrack data, and I only see this host’s IPs.

Observed on this machine:

• enx00e04c680050: 1.1.3.9/24 (default route via 1.1.3.1)
• wlp2s0: 172.16.4.9/23 (secondary default route)

Neighbors seen (LAN devices): 1.1.3.11, 1.1.3.14, 1.1.3.240, 1.1.3.1, 172.16.5.156, 172.16.4.1.

Grant sudo and I’ll capture/attribute the top talker (tcpdump/conntrack).
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
printf "--- live outbound talkers (5s sample) ---\n"
bash: printf: --: 无效的选项
printf: 用法:printf [-v var] 格式 [参数]
👋 再见!
test@wll:~$

Summary by CodeRabbit

  • Bug Fixes

    • Shell now attempts to recover and restart when PTY unexpectedly closes, instead of stopping unconditionally.
    • Multiline bash commands now properly bypass shared PTY execution to prevent compatibility issues.
  • Tests

    • Added test coverage for PTY recovery behavior and multiline command handling.

Review Change Stack

@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 May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b1c95fdc-4994-48d9-b32e-80de213bb8af

📥 Commits

Reviewing files that changed from the base of the PR and between 1494f9c and 5976154.

📒 Files selected for processing (4)
  • src/aish/shell/runtime/app.py
  • src/aish/tools/code_exec.py
  • tests/shell/runtime/test_shell_pty_core.py
  • tests/tools/test_bash_output_offload.py

📝 Walkthrough

Walkthrough

The PR makes the PTY session manager more resilient by introducing two complementary safeguards. Multiline bash commands now bypass the shared PTY path to prevent unexpected termination, and when the PTY unexpectedly exits, the shell attempts recovery via restart instead of stopping unconditionally.

Changes

PTY Resilience

Layer / File(s) Summary
Multiline bash detection and shared PTY bypass
src/aish/tools/code_exec.py, tests/tools/test_bash_output_offload.py
_is_multiline_bash helper classifies commands containing newlines; BashTool.__call__ updated to require non-multiline status before using shared PTY, forcing multiline scripts through non-PTY execution paths. Relaxed stdout assertion and replaced multiline test with assertion that shared PTY is bypassed.
PTY exit recovery and restart handling
src/aish/shell/runtime/app.py, tests/shell/runtime/test_shell_pty_core.py
_shell_exiting handler now conditionally attempts PTY restart via _should_exit_on_pty_close() and _restart_pty() before stopping; only terminates if restart fails or explicit user exit detected. Three new tests verify recovery path is triggered on unexpected exits, explicit user exits skip recovery, and shell stops when restart fails.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • AI-Shell-Team/aish#135: Both PRs modify src/aish/tools/code_exec.py's BashTool backend-routing logic for deciding when to use the shared PTY (main PR for multiline scripts, retrieved PR for interactive sudo/su), so the changes overlap in the same PTY-selection area.

Suggested labels

size: M, tests, tools, experienced-contributor

Poem

🐰 The PTY once fell and the shell did cease,
But now it springs back—what sweet release!
Multiline scripts find a safer way,
As recovery magic saves the day!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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.
Title check ✅ Passed The title clearly summarizes the main bug fix: isolating multiline AI bash commands from the shared PTY to prevent unexpected shell exits and session drops.
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.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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