Skip to content

fix: handle missing files in send_message_to_user#8105

Open
idiotsj wants to merge 1 commit intoAstrBotDevs:masterfrom
idiotsj:submit/fix-send-message-missing-file
Open

fix: handle missing files in send_message_to_user#8105
idiotsj wants to merge 1 commit intoAstrBotDevs:masterfrom
idiotsj:submit/fix-send-message-missing-file

Conversation

@idiotsj
Copy link
Copy Markdown
Contributor

@idiotsj idiotsj commented May 9, 2026

Summary

  • validate local media/file paths before building send_message_to_user components
  • convert proactive send exceptions into error: tool results instead of letting them escape the agent loop
  • add regression tests for missing file paths and send-stage FileNotFoundError

Problem

When an Agent called send_message_to_user with a nonexistent local image path, a FileNotFoundError could bubble out of the tool call path. In practice this could terminate the current agent flow before the model had a chance to fallback to a plain-text reply.

Root Cause

send_message_to_user already wrapped component construction, but it did not explicitly reject unresolved file paths before building file-based components, and it also let exceptions from the final send_message(...) call propagate unless the lower layer swallowed them.

Fix

  • require image / record / video / file path targets to exist after sandbox/local resolution
  • return a normal error: tool result when the final proactive send raises, including FileNotFoundError
  • keep the change scoped to the tool layer so agent scheduling/follow-up behavior is unchanged

Verification

  • python -m pytest tests/unit/test_message_tools.py -q
  • result: 8 passed

Notes

This is intentionally a minimal defensive fix so the LLM can receive the tool error and choose a fallback response instead of having the tool exception abort the run.

Summary by Sourcery

Handle missing local file paths in send_message_to_user and return tool-level error results instead of propagating exceptions.

Bug Fixes:

  • Validate local image, record, video, and file paths before constructing message components, returning an error when a file is missing instead of calling send_message.
  • Trap exceptions raised by the underlying send_message call and surface them as structured error tool results rather than letting them escape the agent loop.

Tests:

  • Add regression tests covering missing local image paths and exceptions raised during the final send_message call.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend labels May 9, 2026
Copy link
Copy Markdown
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 - I've reviewed your changes and they look great!


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.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces file existence validation for media attachments and implements robust error handling for proactive message delivery. It also adds unit tests to verify these error scenarios. The feedback suggests refactoring the repeated path resolution and file check logic into a single helper method to minimize code duplication across various message components.

Comment on lines +122 to +129
def _require_existing_file(self, resolved_path: str, original_path: str) -> str:
if os.path.isfile(resolved_path):
return resolved_path
raise FileNotFoundError(
errno.ENOENT,
os.strerror(errno.ENOENT),
original_path,
)
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.

medium

To reduce code duplication across the different message types, consider combining the path resolution and existence check into a single helper method. This aligns with the general rule of refactoring similar functionality into shared helpers. Additionally, ensure that this new attachment handling functionality is accompanied by corresponding unit tests.

Suggested change
def _require_existing_file(self, resolved_path: str, original_path: str) -> str:
if os.path.isfile(resolved_path):
return resolved_path
raise FileNotFoundError(
errno.ENOENT,
os.strerror(errno.ENOENT),
original_path,
)
async def _resolve_and_require_file(self, context: ContextWrapper[AstrAgentContext], path: str) -> str:
local_path, _ = await self._resolve_path_from_sandbox(context, path)
if os.path.isfile(local_path):
return local_path
raise FileNotFoundError(
errno.ENOENT,
os.strerror(errno.ENOENT),
path)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.
  2. New functionality, such as handling attachments, should be accompanied by corresponding unit tests.

Comment on lines 167 to +170
local_path, _ = await self._resolve_path_from_sandbox(
context, path
)
local_path = self._require_existing_file(local_path, path)
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.

medium

Use the suggested _resolve_and_require_file helper to simplify the code and reduce duplication, following the repository's refactoring guidelines.

                        local_path = await self._resolve_and_require_file(context, path)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

Comment on lines 180 to +183
local_path, _ = await self._resolve_path_from_sandbox(
context, path
)
local_path = self._require_existing_file(local_path, path)
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.

medium

Use the suggested _resolve_and_require_file helper to simplify the code and reduce duplication, following the repository's refactoring guidelines.

                        local_path = await self._resolve_and_require_file(context, path)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

Comment on lines 193 to +196
local_path, _ = await self._resolve_path_from_sandbox(
context, path
)
local_path = self._require_existing_file(local_path, path)
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.

medium

Use the suggested _resolve_and_require_file helper to simplify the code and reduce duplication, following the repository's refactoring guidelines.

                        local_path = await self._resolve_and_require_file(context, path)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

Comment on lines 212 to +215
local_path, _ = await self._resolve_path_from_sandbox(
context, path
)
local_path = self._require_existing_file(local_path, path)
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.

medium

Use the suggested _resolve_and_require_file helper to simplify the code and reduce duplication, following the repository's refactoring guidelines.

                        local_path = await self._resolve_and_require_file(context, path)
References
  1. When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.

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 size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant