Skip to content

fix: handle pip install execution in frozen runtime#4985

Merged
Soulter merged 2 commits intoAstrBotDevs:masterfrom
zouyonghe:master
Feb 9, 2026
Merged

fix: handle pip install execution in frozen runtime#4985
Soulter merged 2 commits intoAstrBotDevs:masterfrom
zouyonghe:master

Conversation

@zouyonghe
Copy link
Collaborator

@zouyonghe zouyonghe commented Feb 9, 2026

Modifications / 改动点

修复桌面客户端,安装python包失败的问题

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

d3765a19fb9284fbf2ce799ab639508f

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

通过优先使用检测到的 Python 可执行文件以子进程方式调用 pip,并在必要时回退到进程内 pip 调用的方式,提高基于 pip 的软件包安装的可靠性。

Bug Fixes:

  • 通过更健壮地选择适用于以子进程方式执行 pip 的 Python 可执行文件,并在子进程执行不可用时回退到进程内 pip,修复在冻结运行环境中桌面客户端安装 Python 软件包失败的问题。

Enhancements:

  • 优化启动 pip 子进程的逻辑,以探测多个候选 Python 可执行文件,并为失败及回退行为添加日志记录。
Original summary in English

Summary by Sourcery

Improve reliability of pip-based package installation by preferring a subprocess invocation with a detected Python executable and falling back to in-process pip when necessary.

Bug Fixes:

  • Fix desktop client Python package installation failures in frozen runtimes by robustly selecting a suitable Python executable for pip subprocess execution and falling back to in-process pip if subprocess execution is unavailable.

Enhancements:

  • Refine pip subprocess launching logic to probe multiple candidate Python executables and add logging for failures and fallback behavior.
Original summary in English

Summary by Sourcery

通过优先使用检测到的 Python 可执行文件以子进程方式调用 pip,并在必要时回退到进程内 pip 调用的方式,提高基于 pip 的软件包安装的可靠性。

Bug Fixes:

  • 通过更健壮地选择适用于以子进程方式执行 pip 的 Python 可执行文件,并在子进程执行不可用时回退到进程内 pip,修复在冻结运行环境中桌面客户端安装 Python 软件包失败的问题。

Enhancements:

  • 优化启动 pip 子进程的逻辑,以探测多个候选 Python 可执行文件,并为失败及回退行为添加日志记录。
Original summary in English

Summary by Sourcery

Improve reliability of pip-based package installation by preferring a subprocess invocation with a detected Python executable and falling back to in-process pip when necessary.

Bug Fixes:

  • Fix desktop client Python package installation failures in frozen runtimes by robustly selecting a suitable Python executable for pip subprocess execution and falling back to in-process pip if subprocess execution is unavailable.

Enhancements:

  • Refine pip subprocess launching logic to probe multiple candidate Python executables and add logging for failures and fallback behavior.

@auto-assign auto-assign bot requested review from Fridemn and Raven95676 February 9, 2026 05:42
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Feb 9, 2026
@dosubot
Copy link

dosubot bot commented Feb 9, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并留下了一些总体反馈:

  • _get_pip_subprocess_executable 中,依赖 'python' in Path(candidate).name.lower() 这种方式,可能会在 frozen/重命名的环境中遗漏有效的解释器;在返回 None 之前,可以考虑使用更健壮的检查方式(例如检查文件是否存在且可执行,或者回退到 shutil.which('python'))。
  • _get_pip_subprocess_executable 返回 None 时,当前会静默地回退到进程内模式;在这一分支里增加一条调试日志,会有助于理解为什么在某些环境中没有使用子进程模式。
供 AI Agent 使用的提示
Please address the comments from this code review:

## Overall Comments
- In `_get_pip_subprocess_executable`, relying on `'python' in Path(candidate).name.lower()` may miss valid interpreters in frozen/renamed environments; consider a more robust check (e.g., checking file existence and executability or falling back to `shutil.which('python')`) before returning `None`.
- When `_get_pip_subprocess_executable` returns `None`, it silently falls back to in-process mode; adding a debug log in that branch would make it easier to understand why subprocess mode is not used in certain environments.

## Individual Comments

### Comment 1
<location> `astrbot/core/utils/pip_installer.py:49-50` </location>
<code_context>
+
+
 def _get_pip_main():
     try:
         from pip._internal.cli.main import main as pip_main
</code_context>

<issue_to_address>
**suggestion:** Catching only FileNotFoundError may miss other subprocess launch failures like PermissionError.

If the chosen `subprocess_executable` exists but isn’t actually runnable (wrong permissions, it’s a directory, other OS issues), `asyncio.create_subprocess_exec` will raise `PermissionError`/`OSError` and bypass the in‑process fallback. Consider catching `OSError` (or `Exception` if you prefer) and then falling back to `_run_pip_in_process`, optionally logging the exception for debugging.

Suggested implementation:

```python
            except OSError as exc:
                logger.warning(
                    "Failed to launch pip subprocess (%r). Falling back to in-process pip: %s",
                    subprocess_executable,
                    exc,
                )

```

If the existing `except` block around `asyncio.create_subprocess_exec` contains different logging or additional logic, you should:
1. Replace the entire `except FileNotFoundError:` block (including its body) with the `except OSError as exc:` block above.
2. Ensure that the logic which falls back to `_run_pip_in_process` is still triggered when the subprocess launch fails (typically by leaving `result_code` as `None` so the later fallback code executes).
3. If `logger` is not available in this scope or uses a different name, adjust the logging call accordingly.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进之后的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In _get_pip_subprocess_executable, relying on 'python' in Path(candidate).name.lower() may miss valid interpreters in frozen/renamed environments; consider a more robust check (e.g., checking file existence and executability or falling back to shutil.which('python')) before returning None.
  • When _get_pip_subprocess_executable returns None, it silently falls back to in-process mode; adding a debug log in that branch would make it easier to understand why subprocess mode is not used in certain environments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_get_pip_subprocess_executable`, relying on `'python' in Path(candidate).name.lower()` may miss valid interpreters in frozen/renamed environments; consider a more robust check (e.g., checking file existence and executability or falling back to `shutil.which('python')`) before returning `None`.
- When `_get_pip_subprocess_executable` returns `None`, it silently falls back to in-process mode; adding a debug log in that branch would make it easier to understand why subprocess mode is not used in certain environments.

## Individual Comments

### Comment 1
<location> `astrbot/core/utils/pip_installer.py:49-50` </location>
<code_context>
+
+
 def _get_pip_main():
     try:
         from pip._internal.cli.main import main as pip_main
</code_context>

<issue_to_address>
**suggestion:** Catching only FileNotFoundError may miss other subprocess launch failures like PermissionError.

If the chosen `subprocess_executable` exists but isn’t actually runnable (wrong permissions, it’s a directory, other OS issues), `asyncio.create_subprocess_exec` will raise `PermissionError`/`OSError` and bypass the in‑process fallback. Consider catching `OSError` (or `Exception` if you prefer) and then falling back to `_run_pip_in_process`, optionally logging the exception for debugging.

Suggested implementation:

```python
            except OSError as exc:
                logger.warning(
                    "Failed to launch pip subprocess (%r). Falling back to in-process pip: %s",
                    subprocess_executable,
                    exc,
                )

```

If the existing `except` block around `asyncio.create_subprocess_exec` contains different logging or additional logic, you should:
1. Replace the entire `except FileNotFoundError:` block (including its body) with the `except OSError as exc:` block above.
2. Ensure that the logic which falls back to `_run_pip_in_process` is still triggered when the subprocess launch fails (typically by leaving `result_code` as `None` so the later fallback code executes).
3. If `logger` is not available in this scope or uses a different name, adjust the logging call accordingly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Feb 9, 2026
@zouyonghe
Copy link
Collaborator Author

@sourcery-ai review

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我在这里给了一些整体性的反馈:

  • 考虑对 _get_pip_subprocess_executable() 的返回结果做缓存(例如通过 functools.lru_cache),这样在每次执行安装调用时就不会反复解析并检查同一批候选可执行文件。
  • 既然子进程的执行现在不再依赖 _is_frozen_runtime(),请再确认一下 _is_frozen_runtime() 在这个模块的其他地方是否仍然被使用;如果没有使用,就可以移除以避免死代码。
提供给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- Consider memoizing the result of `_get_pip_subprocess_executable()` (e.g., via `functools.lru_cache`) so you don't repeatedly resolve and check the same candidate executables on every install call.
- Now that subprocess execution no longer depends on `_is_frozen_runtime()`, double-check whether `_is_frozen_runtime()` is still used elsewhere in this module; if not, it can be removed to avoid dead code.

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've left some high level feedback:

  • Consider memoizing the result of _get_pip_subprocess_executable() (e.g., via functools.lru_cache) so you don't repeatedly resolve and check the same candidate executables on every install call.
  • Now that subprocess execution no longer depends on _is_frozen_runtime(), double-check whether _is_frozen_runtime() is still used elsewhere in this module; if not, it can be removed to avoid dead code.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider memoizing the result of `_get_pip_subprocess_executable()` (e.g., via `functools.lru_cache`) so you don't repeatedly resolve and check the same candidate executables on every install call.
- Now that subprocess execution no longer depends on `_is_frozen_runtime()`, double-check whether `_is_frozen_runtime()` is still used elsewhere in this module; if not, it can be removed to avoid dead code.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 9, 2026
@Soulter Soulter merged commit 2afb08d into AstrBotDevs:master Feb 9, 2026
6 checks passed
Raven95676 added a commit to Raven95676/AstrBot_Rdev that referenced this pull request Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants