{"record":{"id":"e0149f50be3f47b8","repo":"infiniflow/ragflow","slug":"moodle-api-token-is-required","errorCode":null,"errorMessage":"Moodle API token is required","messagePattern":"Moodle API token is required","errorType":"validation","errorClass":"ConnectorMissingCredentialError","httpStatus":null,"severity":"error","filePath":"common/data_source/moodle_connector.py","lineNumber":73,"sourceCode":"        \"\"\"Simplified logging wrapper\"\"\"\n        msg = f\"{context}: {error}\"\n        if level == \"error\":\n            logger.error(msg)\n        else:\n            logger.warning(msg)\n\n    def _get_latest_timestamp(self, *timestamps: int) -> int:\n        \"\"\"Return latest valid timestamp\"\"\"\n        return max((t for t in timestamps if t and t > 0), default=0)\n\n    def _yield_in_batches(self, generator: Generator[Document, None, None]) -> Generator[list[Document], None, None]:\n        for batch in batch_generator(generator, self.batch_size):\n            yield batch\n\n    def load_credentials(self, credentials: dict[str, Any]) -> None:\n        token = credentials.get(\"moodle_token\")\n        if not token:\n            raise ConnectorMissingCredentialError(\"Moodle API token is required\")\n\n        try:\n            self.moodle_client = MoodleClient(self.moodle_url + \"/webservice/rest/server.php\", token)\n            self.moodle_client.core.webservice.get_site_info()\n        except MoodleException as e:\n            if \"invalidtoken\" in str(e).lower():\n                raise CredentialExpiredError(\"Moodle token is invalid or expired\")\n            raise ConnectorMissingCredentialError(f\"Failed to initialize Moodle client: {e}\")\n\n    def validate_connector_settings(self) -> None:\n        if not self.moodle_client:\n            raise ConnectorMissingCredentialError(\"Moodle client not initialized\")\n\n        try:\n            site_info = self.moodle_client.core.webservice.get_site_info()\n            if not site_info.sitename:\n                raise InsufficientPermissionsError(\"Invalid Moodle API response\")\n        except MoodleException as e:","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/moodle_connector.py#L55-L91","documentation":"A ConnectorMissingCredentialError raised in MoodleConnector.load_credentials when the credentials dict has no moodle_token key (or it is falsy). The token is the sole auth mechanism for Moodle's web service API, so the connector refuses to construct its MoodleClient without it.","triggerScenarios":"Calling load_credentials({'moodle_token': ''}) or a dict missing the key entirely — e.g. the credential payload from the UI/backend dropped the field, or an env var was empty.","commonSituations":"Connector credential form saved without pasting the token; env var MOODLE_TOKEN unset in the deployment; key typo (token vs moodle_token); token value of empty string after JSON round-trip.","solutions":["Generate a token in Moodle (Site administration > Plugins > Web services > Manage tokens) and pass it as credentials['moodle_token'].","Verify the dict actually contains a non-empty string under exactly 'moodle_token' before calling load_credentials.","If sourced from env/secrets, confirm the secret is mounted and the name matches."],"exampleFix":"# before\nconnector.load_credentials({})  # raises ConnectorMissingCredentialError('Moodle API token is required')\n\n# after\nconnector.load_credentials({'moodle_token': os.environ['MOODLE_TOKEN']})","handlingStrategy":"validation","validationCode":"token = credentials.get(\"moodle_token\")\nif not isinstance(token, str) or not token.strip():\n    raise ValueError(\"moodle_token missing or empty\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate the credentials dict shape before calling load_credentials.","Store Moodle tokens in a secret manager and strip whitespace on read."],"tags":["moodle","authentication","credentials","connector","configuration"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}