Skip to content

fix(aiocqhttp): send local File segments as base64 to survive split-container deployments - #9772

Open
weed33834 wants to merge 1 commit into
AstrBotDevs:masterfrom
weed33834:fix/file-base64
Open

fix(aiocqhttp): send local File segments as base64 to survive split-container deployments#9772
weed33834 wants to merge 1 commit into
AstrBotDevs:masterfrom
weed33834:fix/file-base64

Conversation

@weed33834

@weed33834 weed33834 commented Aug 22, 2026

Copy link
Copy Markdown

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

  • �strbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py: when a File segment resolves to an existing local absolute path, send its content as �ase64://; otherwise keep the previous behavior.
  • ests/unit/test_aiocqhttp_file_base64.py: new tests covering local-file conversion (content round-trip), URL pass-through, missing-file safety, and the end-to-end private-message dispatch payload.

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 passed

Full 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:

  • Send existing local File segments as base64 data so aiocqhttp clients can receive them without access to AstrBot's filesystem.
  • Preserve remote file URLs and safely handle missing local files without raising errors.

Tests:

  • Add coverage for local-file conversion, content round-tripping, URL pass-through, missing-file handling, and private-message dispatch payloads.

…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
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. labels Aug 22, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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


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.

# 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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Suggested change
bs64 = base64.b64encode(path_obj.read_bytes()).decode()
bs64 = await asyncio.to_thread(lambda: base64.b64encode(path_obj.read_bytes()).decode())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] send_message_to_user 莫名报 no such file,即使 file 存在且权限正确

1 participant