Skip to main content

Error handling

Every SDK exception derives from GleanError and carries a fix_suggestion — and often a docs_url — that are included in str(error). An unhandled failure prints what to do about it, not just what went wrong.

from glean.indexing.exceptions import GleanError

try:
connector.index_data(mode=IndexingMode.FULL)
except GleanError as error:
print(error) # message + "How to fix:" + "Documentation:"
print(error.fix_suggestion)
print(error.docs_url)

Hierarchy​

GleanError
├── GleanConfigurationError (also a ValueError)
│ ├── MissingEnvironmentVariableError
│ └── InvalidDatasourceConfigError
└── GleanValidationError (also a ValueError)
├── InvalidPropertyError
├── InconsistentDataError
└── UnsupportedConnectorTypeError

Configuration errors and validation errors both subclass ValueError, so existing except ValueError handlers keep working.

ExceptionRaised when
MissingEnvironmentVariableErrorGLEAN_SERVER_URL or GLEAN_INDEXING_API_TOKEN is unset. Raised at client construction, not mid-upload.
InvalidDatasourceConfigErrorCustomDatasourceConfig is missing name or display_name.
InvalidPropertyErrorA property definition has an invalid value.
InconsistentDataErrorget_identities() returned groups without memberships.
UnsupportedConnectorTypeErrorA connector type isn't supported for the requested operation.

Source-side errors​

Failures pulling from your source raise different types, because they're not Glean's problem:

ExceptionFrom
PullHttpErrorA source request failed after exhausting retries. Carries status_code and response.
RateLimitExceededErrorRate-limit capacity didn't arrive before rate_limit_timeout_seconds.
from glean.indexing.recipes.pull import PullHttpError

try:
response = self.http.get(f"/articles/{article_id}")
except PullHttpError as error:
if error.status_code == 404:
return None # deleted between listing and fetch — skip it
raise # anything else aborts the crawl

What to swallow, and what not to​

This is the decision that matters most in a connector, because getting it wrong deletes data.

Safe to swallow: a single document that 404s between listing and fetch, or one record whose payload is malformed. Skip it, log it, continue.

Never swallow: anything that makes the result set incomplete — a failed page of results, an auth expiry mid-crawl, a source-side 500 on a list endpoint.

def get_source_data(self, **kwargs):
for summary in self.list_articles(): # raises on failure — correct
try:
yield self.fetch_detail(summary["id"])
except PullHttpError as error:
if error.status_code == 404:
logger.warning("Article %s vanished mid-crawl", summary["id"])
continue
raise
danger

On a full crawl, a short result set means stale-document deletion removes everything that didn't appear. Catching an exception around your paging loop and returning what you have so far converts a transient source outage into mass deletion in Glean. Let it raise. A failed crawl is recoverable; a successful crawl that deleted 90% of your index is not.

Partial upload failures​

The SDK retries transient upload failures via the generated client's retry configuration. If an upload session fails partway, the next run picks up cleanly — but a session left in a stuck state can block subsequent uploads. Clear it with:

connector.index_data(
mode=IndexingMode.FULL,
options=ConnectorOptions(force_restart=True),
)

Use force_restart deliberately, not as a default. It discards in-progress upload state.

Observing failures​

index_data() increments an error counter and re-raises, so failures are visible in metrics as well as logs:

try:
connector.index_data(mode=IndexingMode.FULL)
except Exception:
logger.exception("Crawl failed")
raise # let the scheduler see a non-zero exit

Exiting non-zero matters: a scheduler that thinks the run succeeded won't alert, and won't retry. See Observability.