{"record":{"id":"95c26fc4f15d67d0","repo":"python-telegram-bot/python-telegram-bot","slug":"unknown-bot-instance-found-will-be-replaced-by-n","errorCode":null,"errorMessage":"Unknown bot instance found. Will be replaced by `None` during unpickling","messagePattern":"Unknown bot instance found\\. Will be replaced by `None` during unpickling","errorType":"console","errorClass":"Warning","httpStatus":null,"severity":"warning","filePath":"src/telegram/ext/_picklepersistence.py","lineNumber":98,"sourceCode":"        self, obj: TelegramObj\n    ) -> tuple[Callable, tuple[type[TelegramObj], dict]]:\n        \"\"\"\n        This method is used for pickling. The bot attribute is preserved so\n        _BotPickler().persistent_id works as intended.\n        \"\"\"\n        if not isinstance(obj, TelegramObject):\n            return NotImplemented\n\n        return _custom_reduction(obj)\n\n    def persistent_id(self, obj: object) -> str | None:\n        \"\"\"Used to 'mark' the Bot, so it can be replaced later. See\n        https://docs.python.org/3/library/pickle.html#pickle.Pickler.persistent_id for more info\n        \"\"\"\n        if obj is self._bot:\n            return _REPLACED_KNOWN_BOT\n        if isinstance(obj, Bot):\n            warn(\n                \"Unknown bot instance found. Will be replaced by `None` during unpickling\",\n                stacklevel=2,\n            )\n            return _REPLACED_UNKNOWN_BOT\n        return None  # pickles as usual\n\n\nclass _BotUnpickler(pickle.Unpickler):\n    __slots__ = (\"_bot\",)\n\n    def __init__(self, bot: Bot, *args: Any, **kwargs: Any):\n        self._bot = bot\n        super().__init__(*args, **kwargs)\n\n    def persistent_load(self, pid: str) -> Bot | None:\n        \"\"\"Replaces the bot with the current bot if known, else it is replaced by :obj:`None`.\"\"\"\n        if pid == _REPLACED_KNOWN_BOT:\n            return self._bot","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/python-telegram-bot/python-telegram-bot/blob/d3b69d2e9fb7af6c796f84516cfda751752c7cb7/src/telegram/ext/_picklepersistence.py#L80-L116","documentation":"PicklePersistence's persistent_id hook found a `Bot` instance that is not the persistence's own configured bot. Because Bot instances hold the event loop / HTTPX clients and can't be meaningfully pickled or matched to the running application, PTB replaces it with None when loading and warns so you know data changed.","triggerScenarios":"Creating `PicklePersistence(filepath=...)` without `bot=...` (or with a different Bot instance) and then pickling Update/CallbackContext/ChatData that (indirectly) references a Bot — e.g. storing `context.bot` in `bot_data`/`user_data`, or pickling objects whose attributes reach a Bot. Also when you build persistence for bot A but the stored object references bot B (two Application instances, tests with a fake bot).","commonSituations":"Storing context.bot or context.application (which holds bot) in persistence-backed dicts; running two bot instances sharing one persistence file; unit tests with mock bots while persistence was constructed with a different/real bot; forgetting to pass bot= when constructing PicklePersistence in PTB v20+ (where it's no longer set automatically).","solutions":["Construct persistence with the bot: PicklePersistence(filepath=..., bot=application.bot) so the known bot is replaced correctly on load (set_bot is called by Application when it's built via ApplicationBuilder)","Stop persisting objects that reference Bot: store bot-scoped values like chat_id/token strings instead of context.bot / context.application","If loading legacy data, expect None where the unknown Bot was and re-set the bot at runtime after unpickling","Check bot_data/user_data and any nested objects for accidental Bot references before update_persistence()"],"exampleFix":"# before\npersistence = PicklePersistence(filepath=\"data\")\n# ...\nasync def callback(update, context):\n    context.bot_data[\"bot\"] = context.bot  # pickles an unknown Bot -> replaced by None\n\n# after\npersistence = PicklePersistence(filepath=\"data\")  # bot set via ApplicationBuilder\n# ...\nasync def callback(update, context):\n    context.bot_data[\"chat_id\"] = update.effective_chat.id  # store plain data only","handlingStrategy":"validation","validationCode":"from telegram import Bot\n\ndef has_bot_reference(obj, _seen=None) -> bool:\n    if isinstance(obj, Bot):\n        return True\n    if isinstance(obj, dict):\n        return any(has_bot_reference(v) for v in obj.values())\n    if isinstance(obj, (list, tuple, set)):\n        return any(has_bot_reference(v) for v in obj)\n    return any(\n        has_bot_reference(v) for v in vars(obj).values()\n    ) if hasattr(obj, \"__dict__\") and not isinstance(obj, type) else False\n\n# before update_persistence / dump:\nif has_bot_reference(application.bot_data):\n    logging.warning(\"bot_data holds a Bot instance; it will unpickle as None\")","typeGuard":"from telegram import Bot\n\ndef is_persistable(value) -> bool:\n    \"\"\"True if value (recursively) contains no Bot instance.\"\"\"\n    return not has_bot_reference(value)","tryCatchPattern":null,"preventionTips":["Build persistence through ApplicationBuilder().persistence(PicklePersistence(filepath=...)) so set_bot wires the correct instance","Store identifiers (chat_id, user_id, token) instead of Bot/Application objects in persisted dicts","Never share one persistence file between two Application/Bot instances"],"tags":["telegram","persistence","pickle","python-telegram-bot","bot-instance"],"backgroundTag":"non-serializable-object-pickling","analyzedSha":"d3b69d2e9fb7af6c796f84516cfda751752c7cb7","analyzedAt":"2026-08-28T16:18:54.805Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}