Problem
Uploading two or more files at once via Telegram (a media group) failed with:
no project context available for file upload.
A single file upload to the same chat worked fine. First observed on the
channelo VPS (@hetz_channelo_bot, single project auditor-toolkit, owner
DM) — screenshot showed two documents sent together, then the bot replying with
the error.
Investigation
- Traced the error string to
telegram/commands/file_transfer.py:_prepare_file_put_plan
— it fires when resolved.context / resolved.context.project is None
after cfg.runtime.resolve_message(...).
resolve_message resolves a project from, in order: reply context →
directives → ambient_context → default_project / chat_map. So the
failure means all of those were empty for the upload.
- Compared the two upload code paths:
- Single file →
loop.py:route_message → build_message_context, which
computes ambient_context via a 3-rung fallback ladder: topic-bound →
chat_prefs chat-bound (/ctx) → topic-merged default.
- Media group (2+ files) → coalesced by
MediaGroupBuffer →
commands/media.py:_handle_media_group, which computed ambient_context
from only topic_store + _topics_chat_project — it never consulted
the per-chat ChatPrefsStore.
- Inspected channelo's config + state:
untether.toml has no default_project and the project has no
chat_id (the config validator forbids a project chat_id equal to
transports.telegram.chat_id, and channelo's project chat is the
transport chat).
telegram_chat_prefs_state.json holds context_project: auditor-toolkit
— i.e. the project is bound only via /ctx (chat_prefs).
git log --follow -S on the _handle_media_group ambient-context block:
only two commits ever touched it — today's fix and the pre-0.35.0
takopi → untether rename. The gap is as old as the file.
Root cause
_handle_media_group never consulted ChatPrefsStore. On a deployment where
the project is resolvable solely via the /ctx-bound chat context — no
default_project, no chat_map entry — a media group resolved
ambient_context = None → resolve_message found no project → the error.
Single-file uploads worked because their path (build_message_context) already
had the chat_prefs rung.
Not a v0.35.3 regression
The bug has existed since media.py was introduced (pre-0.35.0). It only
surfaces when the project resolves solely via /ctx-bound context. On lba-1
(multi-project) and nsd (per-project chat_id → chat_map), media groups
resolve via default_project / chat_map regardless. The channelo
single-project-DM deployment (set up ~2026-05-13) is the first config shape to
expose the latent bug — not a code change in any recent release.
Solution
Thread chat_prefs through the media-group path and mirror
build_message_context's fallback ladder:
telegram/commands/media.py — _handle_media_group takes a new
chat_prefs: ChatPrefsStore | None parameter and applies the topic-bound →
chat_prefs chat-bound → topic-merged-default ladder (with a
# keep in sync with loop.py:build_message_context comment).
telegram/loop.py — MediaGroupBuffer._flush_media_group forwards its
already-held self._chat_prefs to handle_media_group(...).
tests/test_telegram_media_command.py — new regression test
(test_media_group_uses_chat_prefs_bound_context): with a chat_prefs stub
and topic_store=None, asserts the media-group path resolves the bound
project. The pre-existing test_media_group_auto_put_* tests only checked
delegation and would not have caught this.
Shipped in PR #563 (squash-merged to dev).
Other actions
- Channelo immediate mitigation — added
default_project = "auditor-toolkit"
to channelo:~/.untether/untether.toml (hot-reloaded, no restart). This
unblocks channelo now, ahead of the code fix reaching it via the next rc
fleet rollout. Recommended as a belt-and-suspenders default for any
single-project deployment.
- CHANGELOG — entry added under
## v0.35.3 → ### fixes.
- Milestone — v0.35.3.
Follow-up (out of scope)
build_message_context and _handle_media_group independently implement
"compute ambient context for an incoming Telegram message". A shared
compute_ambient_context() helper would prevent the next instance of this
divergence class. An interim # keep in sync comment was added.
Affected files
src/untether/telegram/commands/media.py
src/untether/telegram/loop.py
tests/test_telegram_media_command.py
References
Problem
Uploading two or more files at once via Telegram (a media group) failed with:
A single file upload to the same chat worked fine. First observed on the
channelo VPS (
@hetz_channelo_bot, single projectauditor-toolkit, ownerDM) — screenshot showed two documents sent together, then the bot replying with
the error.
Investigation
telegram/commands/file_transfer.py:_prepare_file_put_plan— it fires when
resolved.context/resolved.context.projectisNoneafter
cfg.runtime.resolve_message(...).resolve_messageresolves a project from, in order: reply context →directives →
ambient_context→default_project/chat_map. So thefailure means all of those were empty for the upload.
loop.py:route_message→build_message_context, whichcomputes
ambient_contextvia a 3-rung fallback ladder: topic-bound →chat_prefschat-bound (/ctx) → topic-merged default.MediaGroupBuffer→commands/media.py:_handle_media_group, which computedambient_contextfrom only
topic_store+_topics_chat_project— it never consultedthe per-chat
ChatPrefsStore.untether.tomlhas nodefault_projectand the project has nochat_id(the config validator forbids a projectchat_idequal totransports.telegram.chat_id, and channelo's project chat is thetransport chat).
telegram_chat_prefs_state.jsonholdscontext_project: auditor-toolkit— i.e. the project is bound only via
/ctx(chat_prefs).git log --follow -Son the_handle_media_groupambient-context block:only two commits ever touched it — today's fix and the pre-0.35.0
takopi → untetherrename. The gap is as old as the file.Root cause
_handle_media_groupnever consultedChatPrefsStore. On a deployment wherethe project is resolvable solely via the
/ctx-bound chat context — nodefault_project, nochat_mapentry — a media group resolvedambient_context = None→resolve_messagefound no project → the error.Single-file uploads worked because their path (
build_message_context) alreadyhad the
chat_prefsrung.Not a v0.35.3 regression
The bug has existed since
media.pywas introduced (pre-0.35.0). It onlysurfaces when the project resolves solely via
/ctx-bound context. On lba-1(multi-project) and nsd (per-project
chat_id→chat_map), media groupsresolve via
default_project/chat_mapregardless. The channelosingle-project-DM deployment (set up ~2026-05-13) is the first config shape to
expose the latent bug — not a code change in any recent release.
Solution
Thread
chat_prefsthrough the media-group path and mirrorbuild_message_context's fallback ladder:telegram/commands/media.py—_handle_media_grouptakes a newchat_prefs: ChatPrefsStore | Noneparameter and applies the topic-bound →chat_prefschat-bound → topic-merged-default ladder (with a# keep in sync with loop.py:build_message_contextcomment).telegram/loop.py—MediaGroupBuffer._flush_media_groupforwards itsalready-held
self._chat_prefstohandle_media_group(...).tests/test_telegram_media_command.py— new regression test(
test_media_group_uses_chat_prefs_bound_context): with achat_prefsstuband
topic_store=None, asserts the media-group path resolves the boundproject. The pre-existing
test_media_group_auto_put_*tests only checkeddelegation and would not have caught this.
Shipped in PR #563 (squash-merged to
dev).Other actions
default_project = "auditor-toolkit"to
channelo:~/.untether/untether.toml(hot-reloaded, no restart). Thisunblocks channelo now, ahead of the code fix reaching it via the next rc
fleet rollout. Recommended as a belt-and-suspenders default for any
single-project deployment.
## v0.35.3→### fixes.Follow-up (out of scope)
build_message_contextand_handle_media_groupindependently implement"compute ambient context for an incoming Telegram message". A shared
compute_ambient_context()helper would prevent the next instance of thisdivergence class. An interim
# keep in synccomment was added.Affected files
src/untether/telegram/commands/media.pysrc/untether/telegram/loop.pytests/test_telegram_media_command.pyReferences
dev)