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

  1. Return a tuple `(callback_data, keyboard_data)` of length 2 from get_callback_data, matching what CallbackDataCache.persistence_data yields
  2. 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

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


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