hasura/graphql-engine · error · RequestError::ExplainError

explain error: {0}

Error message

explain error: {0}

What it means

RequestError::ExplainError is a catch-all for failures encountered while producing a query explanation (plan/preview) for a GraphQL request. It carries a String message describing what went wrong during the explain pipeline, prefixed with 'explain error: '. It is distinct from parse/validation errors: the request was understood, but explaining it failed.

Source

Thrown at v3/crates/graphql/frontend/src/error.rs:23

use tracing_util::{ErrorVisibility, TraceableError};

/// Request errors are raised before execution of root fields begins.
/// Ref: <https://spec.graphql.org/October2021/#sec-Errors.Request-errors>
#[derive(Debug, thiserror::Error)]
pub enum RequestError {
    #[error("parsing failed: {0}")]
    ParseFailure(#[from] gql::ast::spanning::Positioned<gql::parser::Error>),

    #[error("validation failed: {0}")]
    ValidationFailed(#[from] gql::validation::Error),

    #[error("{0}")]
    IRConversionError(#[from] graphql_ir::Error),

    #[error("{0}")]
    GraphQlPlanError(#[from] graphql_ir::GraphqlIrPlanError),

    #[error("explain error: {0}")]
    ExplainError(String),
}

impl RequestError {
    pub fn to_graphql_error(&self, expose_internal_errors: ExposeInternalErrors) -> GraphQLError {
        let message = match (self, expose_internal_errors) {
            // Error messages for internal errors from IR conversion and Plan generations are masked.
            (
                Self::IRConversionError(graphql_ir::Error::Internal(_)),
                ExposeInternalErrors::Censor,
            ) => "internal error".into(),
            (e, _) => e.to_string(),
        };
        // We are using the visibility of the error to determine if it is an internal error or not. We are assuming that
        // if we are showing the error message to the user, it is something that they can fix on their end.
        let is_internal = self.visibility() == ErrorVisibility::Internal;
        GraphQLError {
            message,

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Read the embedded String message; it usually names the unsupported construct or failing stage
  2. Try explaining a simplified version of the query to isolate which field/feature breaks explanation
  3. Execute the query normally — if it runs, the issue is explain-specific and can be reported upstream
  4. Update the engine to a version where explain supports the construct in question

Example fix

// before
POST /v1/graphql/explain  { "query": "query { ... complex nested with @include }" }

// after
POST /v1/graphql/explain  { "query": "query { ... simplified top-level field }" }
Defensive patterns

Strategy: fallback

Validate before calling

// Guard explain-only features: reject explain requests for constructs
// your version can't explain before calling the endpoint:
if query.contains_directives(&["@include", "@skip"]) && mode == Explain {
    return Err(ExplainUnsupported.into());
}

Try / catch

let plan = match frontend.explain(&req).await {
    Ok(p) => p,
    Err(RequestError::ExplainError(msg)) => {
        tracing::warn!(%msg, "explain failed; falling back to execution-only path");
        return execution_only(&req).await;
    }
    Err(e) => return Err(e.into()),
};

Prevention

When it happens

Trigger: Invoking the explain/plan-preview endpoint or generating an execution plan explanation for a query whose plan cannot be rendered — for example unsupported constructs in explain mode, IR-to-plan conversion problems during explanation, or downstream errors while formatting the plan for display.

Common situations: Running explain on queries using newer features not yet supported by the explain pipeline; explain endpoints hit during engine upgrades where plan formatting changed; internal errors in the planner surfacing only in explain mode.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/296d7fc377b67a27. Report an issue: GitHub.