fix(maafw): 运行环境坏了要当场知道,别拖到任务中途 - #500
Merged
Merged
Conversation
真机现场:用户的 Python 运行时标准库损坏,运行池却认为环境就绪,直到 worker
起来才在 maa/library.py 第 1 行的 import ctypes 处炸掉,抛出 ctypes 内部的
天书——而此时模拟器和游戏已经先被拉起来了。
补三道:
1. 装完即验(installer._verify_maafw_importable)
原来只有 importlib.metadata.version('maafw'),它读的是包元数据,装了一半
或解释器标准库坏掉时照样报得出版本号。写 manifest 之前真的 import maa
一次,坏环境就不会被记成好的。
2. 跑前自检(embedded_manager.describe_unusable_runtime)
check() 此前只验模式/脚本 ID/项目路径/用户/模拟器,完全不碰环境。现在解析
出该项目会用的 MaaFW requirement,在池里找到匹配的 runtime 才探一次
(一个子进程,解释器本来就要起);没建过就不拦,那份环境会在运行时按需准备。
拦在 check() 而不是只靠编辑页:队列与定时任务不经过编辑页,绕不过 check(),
而且此时模拟器和游戏都还没启动。
为此把 requirement 解析抽成 resolve_project_maafw_requirement,与
prepare_runner_environment 内的顺序一致(项目自带原生库的实测版本优先于
requirements.txt 的声明)。实测对 MaaYYs 解析出 maafw==5.13.0b2,与真机
日志一致。
3. 向导闸门(MaaFWScriptEdit.vue)
环境没准备好不让进下一步——首次要下载 MaaFramework,失败时后面每一步都是
白填。失败不是死路:提示条里带「重试」,会清掉「这个路径已备好过」的记忆
重新准备。
自检自身出问题(池不可读等)一律放行,不能反过来挡住运行。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
审查者指南本 PR 通过“装完即验、跑前自检、向导闸门”三层防线识别损坏的 MaaFW/Python 运行环境:安装阶段实际执行 任务前 MaaFW runtime 验证时序图sequenceDiagram
participant Task as 任务执行
participant Manager as MaaFWEmbeddedManager
participant Resolver as resolve_project_maafw_requirement
participant Pool as MaaFWRuntimePoolService
participant Python as Runtime Python
Task->>Manager: check()
Manager->>Resolver: resolve_project_maafw_requirement(project_path)
Resolver-->>Manager: MaaFW requirement
Manager->>Pool: list()
Pool-->>Manager: 现有 runtimes
alt 存在匹配的 runtime
Manager->>Python: probe_python_identity()
alt runtime 可用
Python-->>Manager: 身份探测成功
Manager-->>Task: 通过
else runtime 不可用
Python-->>Manager: 错误
Manager-->>Task: MFW runtime 不可用
end
else 不存在匹配的 runtime
Manager-->>Task: 通过,按需准备 runtime
end
已安装 MaaFW runtime 验证时序图sequenceDiagram
participant Installer as Runtime 安装器
participant Python as Runtime Python
participant Manifest as Runtime manifest
Installer->>Python: import maa
alt 导入成功
Python-->>Installer: 成功
Installer->>Python: _installed_maafw_version()
Python-->>Installer: MaaFW 版本
Installer->>Manifest: 写入 runtime 元数据
else 导入失败
Python-->>Installer: 导入错误
Installer-->>Manifest: 拒绝写入 manifest
end
MaaFW runtime 环境三层闸门流程图flowchart TD
A[安装或准备 MaaFW runtime] --> B[_verify_maafw_importable]
B -->|import maa 成功| C[写入 runtime manifest]
B -->|import maa 失败| D[拒绝不可用的 runtime]
C --> E[任务检查]
E --> F[resolve_project_maafw_requirement]
F --> G[查找匹配的现有 runtime]
G -->|找到 runtime| H[probe_python_identity]
G -->|未找到 runtime| I[允许检查;运行时准备]
H -->|探测成功| J[启动任务]
H -->|探测失败| K[在启动模拟器和游戏前阻止执行]
A --> L[MaaFWScriptEdit 向导]
L --> M[runAgentEnvPrepare]
M -->|envReady| N[允许进入下一步]
M -->|envFailed| O[显示重试]
O --> P[retryAgentEnvPrepare]
P --> M
文件级变更
提示与命令与 Sourcery 交互
自定义使用体验访问你的控制面板以:
获取帮助Original review guide in EnglishReviewer's Guide本 PR 通过“装完即验、跑前自检、向导闸门”三层防线识别损坏的 MaaFW/Python 运行环境:安装阶段实际执行 Sequence diagram for pre-task MaaFW runtime validationsequenceDiagram
participant Task as Task execution
participant Manager as MaaFWEmbeddedManager
participant Resolver as resolve_project_maafw_requirement
participant Pool as MaaFWRuntimePoolService
participant Python as Runtime Python
Task->>Manager: check()
Manager->>Resolver: resolve_project_maafw_requirement(project_path)
Resolver-->>Manager: MaaFW requirement
Manager->>Pool: list()
Pool-->>Manager: Existing runtimes
alt Matching runtime exists
Manager->>Python: probe_python_identity()
alt Runtime is usable
Python-->>Manager: Identity probe succeeds
Manager-->>Task: Pass
else Runtime is unusable
Python-->>Manager: Error
Manager-->>Task: MFW runtime unavailable
end
else No matching runtime exists
Manager-->>Task: Pass, prepare runtime on demand
end
Sequence diagram for validating an installed MaaFW runtimesequenceDiagram
participant Installer as Runtime installer
participant Python as Runtime Python
participant Manifest as Runtime manifest
Installer->>Python: import maa
alt Import succeeds
Python-->>Installer: Success
Installer->>Python: _installed_maafw_version()
Python-->>Installer: MaaFW version
Installer->>Manifest: Write runtime metadata
else Import fails
Python-->>Installer: Import error
Installer-->>Manifest: Reject manifest write
end
Flow diagram for the three MaaFW runtime environment gatesflowchart TD
A[Install or prepare MaaFW runtime] --> B[_verify_maafw_importable]
B -->|import maa succeeds| C[Write runtime manifest]
B -->|import maa fails| D[Reject unusable runtime]
C --> E[Task check]
E --> F[resolve_project_maafw_requirement]
F --> G[Find matching existing runtime]
G -->|runtime found| H[probe_python_identity]
G -->|runtime not found| I[Allow check; prepare at runtime]
H -->|probe succeeds| J[Start task]
H -->|probe fails| K[Block before emulator and game start]
A --> L[MaaFWScriptEdit wizard]
L --> M[runAgentEnvPrepare]
M -->|envReady| N[Allow next step]
M -->|envFailed| O[Show retry]
O --> P[retryAgentEnvPrepare]
P --> M
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
qiyinxi
added a commit
that referenced
this pull request
Sep 2, 2026
便携版在装有另一份 Python 3.12 的电脑上,MFW 运行环境准备成功、任务一启动就报:
```
File ".../environment/Lib/site-packages/maa/library.py", line 1, in <module>
import ctypes
File "ctypes/__init__.py", line 157, in <module>
AttributeError: class must define a '_type_' attribute
```
## 根因
便携包随附的 `environment/python` 是 embeddable 发行版。运行池拿它当 venv 引导时,uv 把整套
embeddable 复制进 venv 的 `Scripts\`,**但不带 `python312._pth`**。这个 python 找不到
`Lib\os.py` 这个标准库 landmark,CPython 的 `getpath.py` 就按 Windows 回退规则读注册表
`HKCU\Software\Python\PythonCore\3.12\PythonPath`,把本机系统 Python 3.12.10 的
`Lib;DLLs` 排在 venv `Scripts` 之前——3.12.10 的 `_ctypes.pyd` 装进 3.12.0 的
python312.dll。base 解释器自己 `import ctypes` 是好的,所以 #500 的探针只在 venv
上才会红;没装系统 Python 的机器完全不触发,测试端也就没暴露。
## 改动
- 探针新增 `stdlibLandmark`;没有 landmark 的解释器不再作引导(宿主跳过、configured
报错、`install_python_runtime` fail-closed)
- 无显式 python 约束时改按宿主小版本走池内托管解释器,identity 随之取自它;完整 Python 宿主下 runtime id
逐字节不变
- 运行前自检改按项目实际会用的那份 runtime 探测,不再按 MaaFW 版本随便挑(否则旧坏环境会把新好环境拦下)
## 验证
- `pytest tests/task/test_maafw_*.py`:157 passed;`pytest tests
--collect-only` 退出 0;ruff format 通过
- scratch 池端到端:模拟 embeddable 宿主 → 下载托管 CPython 3.12.13 → 建 venv →
`sys.path` 无注册表条目、`import maa` 通过;embeddable 引导被拒
- 真机 D:\AUTO-MAS(beta.2 热补同样改动):M9A 的 `prepare_runner_environment` 建出新
runtime,venv 内 `import maa` 通过
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Sourcery 摘要
确保便携式 MaaFW 环境使用自包含且兼容的 Python 运行时,并可靠地拒绝或修复使用可嵌入式解释器创建的环境。
Bug 修复:
- 防止使用可嵌入式 Python 发行版引导 MaaFW 虚拟环境,避免在 Windows 上混用不兼容的标准库和扩展模块。
- 为便携式主机使用兼容的受管理 Python 运行时;当没有可用的自包含引导解释器时,安全地终止操作。
- 使运行前的运行时验证与项目实际使用的运行时保持一致,从而允许修复环境,同时检测不可用的环境。
增强功能:
- 对于完整的 Python 主机,保留其运行时标识;当便携式主机需要运行时时,则从受管理的引导解释器派生运行时标识。
- 在运行时准备期间,为受管理 Python 的安装提供进度报告。
<details>
<summary>Original summary in English</summary>
## Sourcery 总结
确保便携式 MaaFW 环境使用自包含且兼容的 Python 运行时,并可靠地拒绝或替换由可嵌入式解释器创建的环境。
错误修复:
- 防止可嵌入式 Python 发行版引导 MaaFW 虚拟环境,避免在 Windows 上混用不兼容的标准库和扩展模块。
- 对便携式主机使用由池管理的兼容 Python 运行时;当没有可用的自包含引导解释器时,安全地终止操作。
- 在执行前验证项目实际选择的运行时,从而允许修复损坏的环境,而不会因无关的运行时而阻塞。
增强功能:
- 为完整的 Python 主机保留运行时标识;对于便携式主机,则根据受管理的引导解释器生成标识。
- 在准备受管理的 Python 解释器时报告进度。
<details>
<summary>Original summary in English</summary>
## Sourcery 摘要
确保便携式 MaaFW 环境使用自包含且兼容的 Python 运行时,并可靠地拒绝或替换由可嵌入解释器创建的环境。
错误修复:
- 防止可嵌入 Python 发行版在缺少自包含标准库的情况下引导 MaaFW 虚拟环境。
- 对便携式主机使用由池管理的兼容 Python 运行时;当没有有效的引导解释器可用时,安全地终止操作。
- 在执行前验证项目实际选择的运行时,使过时的环境能够得到修复,而不会因无关的运行时而受阻。
增强功能:
- 保留完整 Python 主机的运行时标识,并根据选定的受管理解释器派生便携式主机的标识。
- 安装受管理的 Python 解释器时报告进度。
<details>
<summary>Original summary in English</summary>
## Summary by Sourcery
Ensure portable MaaFW environments use a self-contained compatible
Python runtime and reliably reject or replace environments created from
embeddable interpreters.
Bug Fixes:
- Prevent embeddable Python distributions from bootstrapping MaaFW
virtual environments without a self-contained standard library.
- Use a compatible pool-managed Python runtime for portable hosts and
fail closed when no valid bootstrap interpreter is available.
- Validate the runtime actually selected for the project before
execution, allowing stale environments to be repaired without blocking
on unrelated runtimes.
Enhancements:
- Preserve runtime identities for complete Python hosts and derive
portable-host identities from the selected managed interpreter.
- Report progress while installing a managed Python interpreter.
</details>
</details>
</details>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
用户机器上的报错:
运行池认为环境就绪,直到 worker 起来才炸——而模拟器和游戏已经先被拉起来了。
根因
ctypes/__init__.py157 行是class py_object(_SimpleCData):,158 行就写着_type_ = "O"。类体里有而_ctypes说没有,只有一种可能:ctypes 的两半(纯 Python 部分与_ctypes.pyd)来自不同构建。原来的
verify_python确实会启动解释器校验,但探针只 importjson/platform/sys/sysconfig,而 version、soabi、platform 全部来自解释器二进制——标准库那一半坏了照样报得一模一样。(探针加 ctypes 的改动已随 dev 上的01a20888合入。)本 PR 补三道
1. 装完即验 —
installer._verify_maafw_importable原来只有
importlib.metadata.version('maafw'),读的是包元数据,装了一半或标准库坏掉时照样报得出版本号。写 manifest 之前真的import maa一次,坏环境就不会被记成好的。2. 跑前自检 —
embedded_manager.describe_unusable_runtimecheck()此前只验模式/脚本 ID/项目路径/用户/模拟器,完全不碰环境。现在解析出该项目会用的 MaaFW requirement,在池里找到匹配的 runtime 才探一次;没建过就不拦,那份环境会在运行时按需准备。拦在
check()而不是只靠编辑页:队列与定时任务不经过编辑页,绕不过它;而且此时模拟器和游戏都还没启动。为此把 requirement 解析抽成
resolve_project_maafw_requirement,与prepare_runner_environment内的顺序一致(项目自带原生库的实测版本优先于requirements.txt的声明)。实测对 MaaYYs 解析出maafw==5.13.0b2,与真机日志一致。3. 向导闸门 —
MaaFWScriptEdit.vue环境没准备好不让进下一步——首次要下载 MaaFramework,失败时后面每一步都是白填。失败不是死路:提示条里带「重试」。
自检自身出问题(池不可读等)一律放行,不能反过来挡住运行。
验证
830 passed,2 条失败均与本 PR 无关(test_activity_fight_preserves_native_options是既有的,test_deleted_script_forces_repo_and_resubscribes来自 feat(BetterGI): 新增更好的原神脚本专项适配 #410)AttributeError: ... has no attribute '_verify_maafw_importable'变红vitest 142 passed、vite build通过未覆盖
向导守卫是 SFC 里的两行 computed,单测要挂载整页,性价比低,没有前端单测。真正可执行的闸门是后端那道(有测试)。
顺带
useBettergiGuiSession.ts:88的 typecheck error 与useScriptApi.ts:1125的no-dupe-else-iflint error 都来自 #410,本 PR 未处理。🤖 Generated with Claude Code
Sourcery 总结
在安装、任务预检和设置向导导航期间,快速失败并阻止使用不可用的 MaaFW 环境。
新功能:
错误修复:
import maa是否成功,从而拒绝损坏的 MaaFW 环境,避免在运行时未就绪时记录该环境。改进:
测试:
Original summary in English
Summary by Sourcery
Fail fast on unusable MaaFW environments during installation, task pre-flight checks, and setup wizard navigation.
New Features:
Bug Fixes:
import maasucceeds before recording the runtime as ready.Enhancements:
Tests: