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
- Optimize the mutation's queries (add indexes, batch/slice the work)
- Break the mutation into smaller operations or a background job
- Raise the GraphQLPostgresTimeout statement timeout if it is set too aggressively
- 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
- Keep mutations small; move bulk work to background jobs
- Add indexes for fields the mutation filters on
- Profile heavy mutations with EXPLAIN ANALYZE
- Add retry-with-backoff for timeouts on idempotent mutations
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Error hiding assignment grades for sections
- operation timed out
- Replica failed to catch up in the allotted time
- and cannot be used together
- and cannot be used together
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::HideAssignmentGradesView on GitHub (pinned to 1c9f0bb801)