fix(aiocqhttp): send local File segments as base64 to survive split-container deployments - #9772
fix(aiocqhttp): send local File segments as base64 to survive split-container deployments#9772weed33834 wants to merge 1 commit into
Conversation
…ontainer deployments Local File segments were passed to the OneBot client as file:// URIs, which fail with ENOENT when the client (e.g. NapCat) runs in another container without a shared volume. Image/Record segments already travel as base64://, so apply the same strategy to locally existing files while keeping http(s) URLs untouched. Fixes AstrBotDevs#9626
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py" line_range="62" />
<code_context>
+ # from this filesystem. Send the content as base64 so
+ # it works without a shared volume. This mirrors how
+ # Image/Record segments are delivered.
+ bs64 = base64.b64encode(path_obj.read_bytes()).decode()
+ d["data"]["file"] = f"base64://{bs64}"
+ return d
# 如果是绝对路径且不包含协议头 (://),则转换为标准的 file: URI
</code_context>
<issue_to_address>
**issue (performance):** `path_obj.read_bytes()` performs the complete file read synchronously inside an async message-sending coroutine, blocking the event loop for the duration of the disk I/O and base64 preparation. Sending a large local file therefore stalls unrelated bot events, heartbeats, and timeouts until the read completes.
**Triggers:** When a large local file is sent while the adapter is handling other asynchronous work.
**Suggested fix:** Move the blocking read and encoding to `asyncio.to_thread` or use an asynchronous file-I/O path.
```suggestion
bs64 = await asyncio.to_thread(lambda: base64.b64encode(path_obj.read_bytes()).decode())
```
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and if the conversion is wrong, file contents may already have been transmitted to the OneBot client or a message may have been sent with an unusable payload before the change is reverted. Reverting restores the previous path-based behavior but cannot recall files or messages that were already sent.
Blocking findings: astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py:62
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| # from this filesystem. Send the content as base64 so | ||
| # it works without a shared volume. This mirrors how | ||
| # Image/Record segments are delivered. | ||
| bs64 = base64.b64encode(path_obj.read_bytes()).decode() |
There was a problem hiding this comment.
issue (performance): path_obj.read_bytes() performs the complete file read synchronously inside an async message-sending coroutine, blocking the event loop for the duration of the disk I/O and base64 preparation. Sending a large local file therefore stalls unrelated bot events, heartbeats, and timeouts until the read completes.
Triggers: When a large local file is sent while the adapter is handling other asynchronous work.
Suggested fix: Move the blocking read and encoding to asyncio.to_thread or use an asynchronous file-I/O path.
| bs64 = base64.b64encode(path_obj.read_bytes()).decode() | |
| bs64 = await asyncio.to_thread(lambda: base64.b64encode(path_obj.read_bytes()).decode()) |
Motivation
Fixes #9626. In split-container deployments (AstrBot and NapCat in different containers), proactive sends of local files failed with ENOENT: no such file or directory. The aiocqhttp adapter passed local paths as ile:// URIs, which the OneBot client cannot read because it does not share AstrBot's filesystem. Image/Record segments already travel as �ase64://, so this applies the same strategy to locally existing File segments while keeping http(s) URLs untouched.
Modifications
No breaking changes. No new dependencies.
Test Results
ext $ uvx --from ruff==0.15.22 ruff format --check . 501 files already formatted $ uvx --from ruff==0.15.22 ruff check . All checks passed! $ TESTING=true python -m pytest tests/unit/test_aiocqhttp_file_base64.py -v 4 passed $ python scripts/smoke_startup_check.py Smoke test passedFull suite on Windows shows the same failure set as a clean upstream master checkout (pre-existing environment-specific symlink/shell tests); zero failures in touched modules.
Summary by Sourcery
Make aiocqhttp file delivery work across split-container deployments by transmitting local files as base64.
Bug Fixes:
Tests: