python-telegram-bot/python-telegram-bot · error · ValueError
callback_data must be a tuple of length 2
Error message
callback_data must be a tuple of length 2
What it means
When persistence stores callback_data and the bot has a callback_data_cache, initialize() loads the persisted callback data, which must be a tuple of length 2 (the persisted callback_data and keyboard_data of the CallbackDataCache). If the persistence layer returns anything other than a 2-tuple, a ValueError is raised.
Source
Thrown at src/telegram/ext/_application.py:581
self._user_data.update(await self.persistence.get_user_data())
if self.persistence.store_data.chat_data:
self._chat_data.update(await self.persistence.get_chat_data())
if self.persistence.store_data.bot_data:
self.bot_data = await self.persistence.get_bot_data()
if not isinstance(self.bot_data, self.context_types.bot_data):
raise ValueError(
f"bot_data must be of type {self.context_types.bot_data.__name__}"
)
# Mypy doesn't know that persistence.set_bot (see above) already checks that
# self.bot is an instance of ExtBot if callback_data should be stored ...
if self.persistence.store_data.callback_data and (
self.bot.callback_data_cache is not None # type: ignore[attr-defined]
):
persistent_data = await self.persistence.get_callback_data()
if persistent_data is not None:
if not isinstance(persistent_data, tuple) or len(persistent_data) != 2:
raise ValueError("callback_data must be a tuple of length 2")
self.bot.callback_data_cache.load_persistence_data( # type: ignore[attr-defined]
persistent_data
)
async def start(self) -> None:
"""Starts
* a background task that fetches updates from :attr:`update_queue` and processes them via
:meth:`process_update`.
* :attr:`job_queue`, if set.
* a background task that calls :meth:`update_persistence` in regular intervals, if
:attr:`persistence` is set.
Note:
This does *not* start fetching updates from Telegram. To fetch updates, you need to
either start :attr:`updater` manually or use one of :meth:`run_polling` or
:meth:`run_webhook`.
View on GitHub (pinned to d3b69d2e9f)
Solutions
- Return a tuple `(callback_data, keyboard_data)` of length 2 from get_callback_data, matching what CallbackDataCache.persistence_data yields
- Delete/migrate stale persisted callback data created by older versions
Defensive patterns
Strategy: validation
Validate before calling
cd = await persistence.get_callback_data() assert cd is None or (isinstance(cd, tuple) and len(cd) == 2)
Type guard
def is_valid_callback_data(cd) -> bool:
return cd is None or (isinstance(cd, tuple) and len(cd) == 2) Prevention
- Implement get_callback_data to echo CallbackDataCache.persistence_data structure
- Test persistence round-trips after upgrades
- Discard incompatible persisted callback data instead of loading it
When it happens
Trigger: A custom persistence implementation whose get_callback_data returns a list, a dict, or a tuple with != 2 elements; corrupted or manually edited persistence files from a prior version.
Common situations: Writing a custom database-backed persistence and misunderstanding the expected format; pickle files created by an older telegram.ext version with a different structure.
Related errors
- bot_data must be of type {self.context_types.bot_data.__name
- callback_data can only be stored when using telegram.ext.Ext
- Unable to deserialize callback_data_json. Not valid JSON
- The parameter `inline_keyboard` should be a sequence of sequ
- The parameter `keyboard` should be a sequence of sequences o
AI-assisted analysis of python-telegram-bot/python-telegram-bot@d3b69d2e9f (2026-08-28).
Data as JSON: /api/errors/80d508fe44006bd3.
Report an issue: GitHub.