python-telegram-bot/python-telegram-bot · error · RuntimeError
Can't build key for update without CallbackQuery!
Error message
Can't build key for update without CallbackQuery!
What it means
When per_message=True, ConversationHandler keys conversations by the message the callback query is attached to. If an update being checked has no callback_query at all (e.g. a Message or InlineQuery update), the key cannot be built and RuntimeError is raised in _get_key.
Source
Thrown at src/telegram/ext/_handlers/conversationhandler.py:649
"""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,
conversation_key: ConversationKey,
) -> None:
try:
effective_new_state = await new_state
except Exception as exc:View on GitHub (pinned to d3b69d2e9f)
Solutions
- Make sure entry_points and all state handlers are CallbackQueryHandler instances when per_message=True
- Add a check_update guard: use a TypeHandler(CallbackQuery, ...) wrapper so only callback updates reach the conversation
- If the conversation must handle both messages and callbacks, use per_message=False with per_chat/per_user instead
Example fix
# before
ch = ConversationHandler(
entry_points=[CommandHandler('start', start)], # message handler + per_message
states={1: [CallbackQueryHandler(one)]},
per_message=True,
)
# after
ch = ConversationHandler(
entry_points=[CallbackQueryHandler(start, pattern='^start$')],
states={1: [CallbackQueryHandler(one)]},
per_message=True,
) Defensive patterns
Strategy: validation
Validate before calling
def is_per_message_safe(update: Update) -> bool:
return update.callback_query is not None
# only feed callback updates to per_message conversations Type guard
def is_callback_update(update: Update) -> bool:
return update.callback_query is not None Prevention
- Use only CallbackQueryHandler in per_message conversations
- Lint your states dict: assert all handlers are CallbackQueryHandler when per_message=True
When it happens
Trigger: ConversationHandler(per_message=True) whose check_update is invoked for a non-callback update (any Message, EditedMessage, InlineQuery, etc.). The per_message branch unconditionally accesses update.callback_query and raises if it is None.
Common situations: Building a per_message conversation but adding entry points or state handlers that match plain messages (common mistake — per_message=True should use CallbackQueryHandler entry points only). Triggered from check_update whenever a text message arrives while the handler is in a state that matches it.
Related errors
- You can not assign a new value to map_to_parent after initia
- This handler is not persistent, has no name or the applicati
- Can't build key for update without effective chat!
- Can't build key for update without effective user!
- callback_data_json is not in the required format
AI-assisted analysis of python-telegram-bot/python-telegram-bot@d3b69d2e9f (2026-08-28).
Data as JSON: /api/errors/caa90903e6cb42b2.
Report an issue: GitHub.