{"record":{"id":"bddad2a4497910fe","repo":"infiniflow/ragflow","slug":"discord","errorCode":null,"errorMessage":"Discord","messagePattern":"Discord","errorType":"validation","errorClass":"ConnectorMissingCredentialError","httpStatus":null,"severity":"error","filePath":"common/data_source/discord_connector.py","lineNumber":254,"sourceCode":"\n    def __init__(\n        self,\n        server_ids: list[str] | None = None,\n        channel_names: list[str] | None = None,\n        # YYYY-MM-DD\n        start_date: str | None = None,\n        batch_size: int = INDEX_BATCH_SIZE,\n    ):\n        self.batch_size = batch_size\n        self.channel_names: list[str] = channel_names if channel_names else []\n        self.server_ids: list[int] = [int(server_id) for server_id in server_ids] if server_ids else []\n        self._discord_bot_token: str | None = None\n        self.requested_start_date_string: str = start_date or \"\"\n\n    @property\n    def discord_bot_token(self) -> str:\n        if self._discord_bot_token is None:\n            raise ConnectorMissingCredentialError(\"Discord\")\n        return self._discord_bot_token\n\n    def _iter_merged_documents(\n        self,\n        start: datetime | None = None,\n        end: datetime | None = None,\n    ) -> GenerateDocumentsOutput:\n        \"\"\"Build merged Discord documents for the requested polling window.\"\"\"\n        doc_batch: list[Document] = []\n\n        def _message_created_at(message: DiscordMessage) -> datetime:\n            created_at = message.created_at\n            if created_at.tzinfo is None:\n                return created_at.replace(tzinfo=timezone.utc)\n            return created_at.astimezone(timezone.utc)\n\n        def _is_in_window(message: DiscordMessage) -> bool:\n            created_at = _message_created_at(message)","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/discord_connector.py#L236-L272","documentation":"Raised by the discord_bot_token property when self._discord_bot_token is None. The token is only set in load_credentials() (via credentials['discord_bot_token']), so any read before that step — or after a credential dict missing that key raised KeyError and was swallowed — trips this guard.","triggerScenarios":"Calling _iter_merged_documents/poll_source/load_from_state before load_credentials; load_credentials raised KeyError('discord_bot_token') earlier and the caller proceeded.","commonSituations":"Scripts constructing DiscordConnector directly; orchestrator catching the KeyError from a malformed credential payload but continuing the run.","solutions":["Call load_credentials({'discord_bot_token': '<token>'}) before any polling/indexing method","Ensure the credential payload actually contains the 'discord_bot_token' key (KeyError in load_credentials means it never landed)","Treat ConnectorMissingCredentialError as non-retriable: fix the credential config, then restart the run"],"exampleFix":"// before\nconnector = DiscordConnector(...)\nconnector.poll_source(start, end)  # raises\n\n// after\nconnector = DiscordConnector(...)\nconnector.load_credentials({'discord_bot_token': token})\nconnector.poll_source(start, end)","handlingStrategy":"validation","validationCode":"if 'discord_bot_token' not in credentials or not credentials['discord_bot_token']:\n    raise ValueError('discord_bot_token required')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Wrap connector construction + load_credentials in a single factory so token setup cannot be skipped","Fail the whole run on ConnectorMissingCredentialError instead of continuing to indexing"],"tags":["discord","credentials","lifecycle","bot-token"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}