fix: scheduler was not attaching media files#4877
Conversation
…mproved environment handling fix(schedfuler): media files not being attached via _send_to_platform()
There was a problem hiding this comment.
Pull request overview
This PR fixes cron scheduler deliveries so extracted MEDIA: attachments are forwarded to the shared _send_to_platform() send path, aligning scheduler behavior with the existing send_message_tool media handling.
Changes:
- Update
cron/scheduler.pyto extractMEDIA:tags viaBasePlatformAdapter.extract_media()and passmedia_files+ cleaned text into_send_to_platform(). - Refactor scheduler internals for clearer delivery flow and improved env injection/cleanup helpers.
- Add a regression test in
tests/cron/test_scheduler.pyverifying that cron delivery passes attachments separately from message text.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
cron/scheduler.py |
Extracts media before delivery and forwards media_files to _send_to_platform(); refactors delivery/tick flow. |
tests/cron/test_scheduler.py |
Adds/updates tests to assert cleaned content + media_files forwarding in cron delivery. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| media_files, cleaned_delivery_content = BasePlatformAdapter.extract_media(delivery_content) | ||
|
|
||
| # Run the async send in a fresh event loop (safe from any thread) | ||
| coro = _send_to_platform(platform, pconfig, chat_id, delivery_content, thread_id=thread_id) | ||
| coro = _send_to_platform( | ||
| platform, |
There was a problem hiding this comment.
BasePlatformAdapter.extract_media() can produce an empty cleaned_delivery_content when the message is only MEDIA: tags. Passing media_files with an empty message into _send_to_platform() will return an error for non-Telegram platforms (it only supports native media on Telegram), causing cron delivery to silently fail where it previously would have delivered the tag text. Consider extracting/passing media_files only for Telegram, or falling back to sending the original delivery_content (or a textual placeholder) when platform != Platform.TELEGRAM and cleaned_delivery_content.strip() is empty.
There was a problem hiding this comment.
This is an existing problem, and should probably be patched in another PR, and most likely be patched in _send_to_platform() directly
My agents response after questioning him:
This is a real issue, but it is narrower than it first sounds.
The cron scheduler extracts `MEDIA:` tags before calling `_send_to_platform()`. On Telegram, that is fine because `_send_to_platform()` supports native media delivery there. On non-Telegram platforms, `_send_to_platform()` explicitly rejects the case where it receives `media_files` with an empty text body.
That means the failure case is:
- the cron response is only `MEDIA:` tags, or `[[audio_as_voice]]` plus `MEDIA:`
- `cron.wrap_response` is disabled
- the delivery target is not Telegram
In that path, cron strips the tags, ends up with an empty message, passes the extracted media into `_send_to_platform()`, and the non-Telegram branch returns an error instead of falling back to text. The scheduler logs the error, but there is no user-facing fallback, so delivery can appear to silently fail.
A useful nuance: with the default config, `cron.wrap_response` is enabled, so cron usually adds header/footer text around the response before media extraction. In that default path, the cleaned message is usually not empty, so delivery does not completely fail. However, the media is still omitted on non-Telegram platforms, and the warning about that omission is not surfaced to the end user. So the default behavior is usually degraded delivery, not total delivery failure.
So the right conclusion is:
- yes, this is a real bug
- no, it is not a broad default-path outage
- it is primarily an edge-case failure for media-only cron output on non-Telegram targets
- even outside that edge case, non-Telegram cron delivery can silently lose media content
The cleanest fixes would be either:
1. In cron delivery, only extract and pass `media_files` for Telegram, and otherwise send the original text or a fallback placeholder when media stripping leaves the message empty.
2. In `_send_to_platform()`, make non-Telegram media-only sends degrade gracefully to text instead of returning an error.
I would treat this as worth fixing because the current behavior is inconsistent and can fail in a way that is hard for users to understand.
| except OSError: | ||
| logger.debug("Tick skipped — another instance holds the lock") | ||
| if lock_fd is not None: | ||
| lock_fd.close() | ||
| return 0 |
There was a problem hiding this comment.
The outer except OSError: around the whole tick body will also catch unrelated filesystem errors from get_due_jobs() / job processing and misreport them as “another instance holds the lock”, returning 0 and hiding the real failure. Please narrow this handler to only lock-acquisition/locking operations (or log the exception details when it’s not a lock contention case).
|
The media attachment problem is I also did some cleanup Everything is GREEN with pylance in standards mode and ruff using the following settings |
|
PS. ya'll will need to update the node runtimes in the test.yml workflows : GH is failing with Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4, astral-sh/setup-uv@v5. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. |
|
I'd also move to at least python 3.12 It brings some pretty big performance improvements, especially around async stuff, which can be interresting for agents and jobs |
The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR #4877 by robert-hoffmann.
The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR #4877 by robert-hoffmann.
|
Merged via PR #5598. Your media extraction fix was cherry-picked onto current main — the core fix (extract_media + media_files forwarding) is preserved. The unrelated cosmetic/refactoring changes were skipped to keep the diff focused. Thanks for catching this bug, @robert-hoffmann! |
|
Something didn't go right @teknium1 I updated to the latest version which includes the patch, but something changed in the code and the attachment still doesnt go through, seems there were other code changes in the mean-time.. Here is Herme's diagnostic: |
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
…rch#5598) The cron scheduler delivery path passed raw text including MEDIA: tags to _send_to_platform(), so media attachments were delivered as literal text instead of actual files. The send function already supports media_files= but the cron path never used it. Now calls BasePlatformAdapter.extract_media() to split media paths from text before sending, matching the gateway's normal message flow. Salvaged from PR NousResearch#4877 by robert-hoffmann.
fix(scheduler): forward media_files to _send_to_platform()
What does this PR do?
Fixes cron delivery so media attachments are forwarded correctly when scheduler jobs send results through
_send_to_platform().The underlying send path in
tools/send_message_tool.pyalready supportedMEDIA:extraction and native file delivery, but the cron scheduler delivery path was still only passing message text. As a result, cron jobs could deliver cleaned text while dropping the extracted attachment payload.This change updates
cron/scheduler.pyto:cleaned_delivery_contentto_send_to_platform(), andmedia_files=media_filesso attachments are actually delivered.While touching this path, the scheduler also received focused cleanup around documentation, environment handling, and delivery-flow readability, without changing the intended behavior outside this fix.
Related Issue
Fixes #
Type of Change
Changes Made
cron/scheduler.pyso cron delivery forwards extractedmedia_filesinto_send_to_platform()cleaned_delivery_contentinstead of the raw wrapped body after media extractiontests/cron/test_scheduler.pyto verify cron delivery passes attachments separately from message textHow to Test
Run the focused scheduler test suite: