python-telegram-bot/python-telegram-bot · error · RuntimeError

Can't build key for update without effective user!

Error message

Can't build key for update without effective user!

What it means

When a ConversationHandler is created with per_user=True, the conversation key includes update.effective_user.id. Updates that carry no user (rare, e.g. some channel posts or malformed updates) make key construction impossible, so RuntimeError is raised in _get_key during check_update.

Source

Thrown at src/telegram/ext/_handlers/conversationhandler.py:644

            )

        return out

    def _get_key(self, update: Update) -> ConversationKey:
        """Builds the conversation key associated with the update."""
        chat = update.effective_chat
        user = update.effective_user

        key: list[int | str] = []

        if self.per_chat:
            if chat is None:
                raise RuntimeError("Can't build key for update without effective chat!")
            key.append(chat.id)

        if self.per_user:
            if user is None:
                raise RuntimeError("Can't build key for update without effective user!")
            key.append(user.id)

        if self.per_message:
            if update.callback_query is None:
                raise RuntimeError("Can't build key for update without CallbackQuery!")
            if update.callback_query.inline_message_id:
                key.append(update.callback_query.inline_message_id)
            else:
                key.append(update.callback_query.message.message_id)  # type: ignore[union-attr]

        return tuple(key)

    async def _schedule_job_delayed(
        self,
        new_state: asyncio.Task,
        application: "Application[Any, CCT, Any, Any, Any, JobQueue]",
        update: Update,
        context: CCT,

View on GitHub (pinned to d3b69d2e9f)

Solutions

  1. Set per_user=False for handlers that must process channel posts
  2. Restrict the update types the application fetches (Application.builder().updates(...)) or use a dedicated TypeHandler/channel_post handler instead of the conversation
  3. Use per_chat=True keyed conversations for channel content

Example fix

# before
ch = ConversationHandler(..., per_user=True)  # receives channel posts

# after
ch = ConversationHandler(..., per_user=False, per_chat=True)
# or exclude channel posts: Application.builder().updates(Update.ALL_TYPES[:15]).build()
Defensive patterns

Strategy: validation

Validate before calling

def can_handle(ch, update):
    return not (ch.per_user and update.effective_user is None)

Type guard

def has_effective_user(update: Update) -> bool:
    return update.effective_user is not None

Prevention

When it happens

Trigger: ConversationHandler(per_user=True) processing an Update where effective_user is None — typically channel_post updates from anonymous channel admins or edited channel posts. Raised inside _get_key called from check_update.

Common situations: Handling channel posts or edited channel posts in a per_user conversation. Channel posts authored by channels themselves have sender_chat instead of from_user, leaving effective_user None.

Related errors


AI-assisted analysis of python-telegram-bot/python-telegram-bot@d3b69d2e9f (2026-08-28). Data as JSON: /api/errors/6ebe7f4e49124095. Report an issue: GitHub.