infiniflow/ragflow · error · ConnectorValidationError

Failed to fetch courses: {e}

Error message

Failed to fetch courses: {e}

What it means

A ConnectorValidationError raised in _get_enrolled_courses when core.course.get_courses() throws a MoodleException. The raw Moodle error text is embedded, and because the helper is wrapped in @retry(tries=3, delay=1, backoff=2) Moodle-side errors are retried three times before this surfaces. Unlike its sibling _get_course_contents (which swallows errors and returns []), the course fetch is treated as fatal — without courses there is nothing to index.

Source

Thrown at common/data_source/moodle_connector.py:210

                    f"slim snapshot for course {getattr(course, 'fullname', '?')}",
                    e,
                )

        if batch:
            yield batch

        logger.info(f"Moodle slim snapshot completed: {total} documents listed")

    @retry(tries=3, delay=1, backoff=2)
    def _get_enrolled_courses(self) -> list:
        if not self.moodle_client:
            raise ConnectorMissingCredentialError("Moodle client not initialized")

        try:
            return self.moodle_client.core.course.get_courses()
        except MoodleException as e:
            self._log_error("fetching courses", e, "error")
            raise ConnectorValidationError(f"Failed to fetch courses: {e}")

    @retry(tries=3, delay=1, backoff=2)
    def _get_course_contents(self, course_id: int):
        if not self.moodle_client:
            raise ConnectorMissingCredentialError("Moodle client not initialized")

        try:
            return self.moodle_client.core.course.get_contents(courseid=course_id)
        except MoodleException as e:
            self._log_error(f"fetching course contents for {course_id}", e)
            return []

    def _process_courses(self, courses) -> Generator[Document, None, None]:
        for course in courses:
            try:
                contents = self._get_course_contents(course.id)
                for section in contents:
                    for module in section.modules:

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Read the embedded {e} text — Moodle error codes (invalidtoken, accessexception, missing capability, invalidfunction) each point to a distinct fix.
  2. Ensure the token's service includes core_course_get_courses (and core_course_get_contents for later steps), or use an admin-issued token.
  3. Grant the token user the moodle/course:view capability on the courses to index.
  4. Verify directly: curl '<moodle_url>/webservice/rest/server.php?wstoken=TOKEN&wsfunction=core_course_get_courses&moodlewsrestformat=json'.

Example fix

# before
connector.load_credentials(creds)
for batch in connector.load_from_state():  # ConnectorValidationError('Failed to fetch courses: ...')
    ...

# after
# fix token service scope in Moodle (include core_course_get_courses), then
connector.load_credentials(new_creds)
import requests
r = requests.get(f"{moodle_url}/webservice/rest/server.php", params={"wstoken": token, "wsfunction": "core_course_get_courses", "moodlewsrestformat": "json"})
r.raise_for_status()  # confirm no exception block in response before re-running the load
Defensive patterns

Strategy: retry

Validate before calling

import requests

def can_fetch_courses(moodle_url: str, token: str) -> bool:
    r = requests.get(
        f"{moodle_url}/webservice/rest/server.php",
        params={"wstoken": token, "wsfunction": "core_course_get_courses", "moodlewsrestformat": "json"},
        timeout=10,
    )
    data = r.json()
    return "exception" not in data

Try / catch

try:
    for batch in connector.load_from_state():
        process(batch)
except ConnectorValidationError as exc:
    if not str(exc).startswith("Failed to fetch courses"):
        raise
    msg = str(exc).lower()
    if "invalidtoken" in msg:
        refresh_moodle_credentials(connector)
    elif "accessexception" in msg or "capability" in msg:
        notify_admin_to_grant_course_access()
    else:
        raise

Prevention

When it happens

Trigger: Calling any load path when Moodle rejects core_course_get_courses — token's service lacks that function, user lacks capability to view courses, or Moodle returns an error like 'accessexception'/'invalidtoken' at course-list time.

Common situations: Token created on a custom service that did not include core_course_get_courses; user role can authenticate but not list courses; Moodle upgrade that renamed/removed the function; debugging confusion from the triple retry in logs.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/e8b2c5235b49039d. Report an issue: GitHub.