instructure/canvas-lms · error · GraphQL::ExecutionError

operation timed out

Error message

operation timed out

What it means

PostgresTimeoutFieldExtension wraps every mutation resolution in GraphQLPostgresTimeout.wrap; if the mutation's SQL exceeds the configured statement timeout, GraphQLPostgresTimeout::Error is rescued and re-raised as GraphQL::ExecutionError 'operation timed out'.

Solutions

  1. Optimize the mutation's queries (add indexes, batch/slice the work)
  2. Break the mutation into smaller operations or a background job
  3. Raise the GraphQLPostgresTimeout statement timeout if it is set too aggressively
  4. Profile the offending mutation with EXPLAIN ANALYZE to find the slow statement

Example fix

// before
mutation { updateGrades(input: { courseGid: ..., allGradeData: <10k rows> }) }  // times out
// after
// chunk into a background job, mutate in slices:
updateGrades(input: { courseGid: ..., gradeData: <500-row slice> })
Defensive patterns

Strategy: retry

Validate before calling

// client: keep mutation payloads small
if (payloadSize > CHUNK_LIMIT) scheduleBackgroundJob(payload)

Try / catch

try { await mutate(UPDATE) } catch (e) { if (e.message === 'operation timed out') { await retryWithSmallerBatch(mutate, UPDATE) } else throw e }

Prevention

When it happens

Trigger: Any mutation whose database work exceeds the PostgreSQL statement timeout set by GraphQLPostgresTimeout (large batch updates, missing indexes, huge submissions/course datasets).

Common situations: Timeout configured too low for heavy mutations; unindexed queries under new data volumes; long-running bulk import mutations; DB load spikes pushing previously-fast queries over the limit.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/51c8ae6fae57313e. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/types/mutation_type.rb:27

# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

class PostgresTimeoutFieldExtension < GraphQL::Schema::FieldExtension
  def resolve(object:, arguments:, context:, **)
    GraphQLPostgresTimeout.wrap(context.query) do
      yield(object, arguments)
    end
  rescue GraphQLPostgresTimeout::Error
    raise GraphQL::ExecutionError, "operation timed out"
  end
end

class Types::MutationType < Types::ApplicationObjectType
  graphql_name "Mutation"

  ##
  # wraps all mutation fields with necessary
  # extensions (e.g. pg timeout)
  def self.field(*, **)
    super(*, **, extensions: [PostgresTimeoutFieldExtension, AuditLogFieldExtension])
  end

  field :add_conversation_message, mutation: Mutations::AddConversationMessage
  field :create_conversation, mutation: Mutations::CreateConversation
  field :create_group_in_set, mutation: Mutations::CreateGroupInSet
  field :create_group_set, mutation: Mutations::CreateGroupSet
  field :hide_assignment_grades, mutation: Mutations::HideAssignmentGrades

View on GitHub (pinned to 1c9f0bb801)