gatsbyjs/gatsby · error · OperationError

Operation ${id} failed with ${errorCode}

Error message

Operation ${id} failed with ${errorCode}

What it means

Thrown by completedOperation when a Shopify bulk operation reaches a failed terminal status (`FAILED` or `CANCELED`). The OperationError carries the full bulk operation node (error.node) so callers can read errorCode, id, and status. Common errorCodes include ACCESS_DENIED, TIMEOUT, and schema validation failures. The poll loop checks status on an interval and exits as soon as a failed status is observed.

Source

Thrown at packages/gatsby-source-shopify/src/create-operations.ts:188

   * surface feedback to the user suggesting that they increase
   * the interval.
   */
  async function completedOperation(
    operationId: string,
    interval = 1000
  ): Promise<{ node: IBulkOperationNode }> {
    let operation = await graphqlClient.request<{
      node: IBulkOperationNode
    }>(OPERATION_BY_ID, {
      id: operationId,
    })

    let waitForOperation = true

    while (waitForOperation) {
      if (failedStatuses.includes(operation.node.status)) {
        waitForOperation = false
        throw new OperationError(operation.node)
      }

      if (operation.node.status === `COMPLETED`) {
        waitForOperation = false
        return operation
      }

      await new Promise(resolve => setTimeout(resolve, interval))

      operation = await graphqlClient.request<{
        node: IBulkOperationNode
      }>(OPERATION_BY_ID, {
        id: operationId,
      })
    }

    throw new Error(`It should never reach this error`)
  }

View on GitHub (pinned to 8b06340921)

Solutions

  1. Inspect error.node.errorCode — for ACCESS_DENIED, update the Shopify app's access scopes to include the required read permissions.
  2. Re-run the failing bulk query manually against the Shopify Admin API to capture the precise error message.
  3. Confirm the apiVersion in plugin options matches the schema the query targets.
  4. For TIMEOUT/CANCELED on very large stores, narrow the query (e.g. filter by updated_at) to reduce operation size.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isOperationError(e): e is Error & { node: { errorCode: string; id: string; status: string } } { return e instanceof Error && 'node' in e && typeof (e as any).node?.errorCode === 'string' }

Try / catch

try { await completedOperation(opId) } catch (e) { if (isOperationError(e)) { if (e.node.errorCode === 'ACCESS_DENIED') reporter.panic('Shopify app missing required access scopes'); else reporter.warn(`Bulk op ${e.node.id} failed (${e.node.errorCode}); retrying`) } throw e }

Prevention

When it happens

Trigger: Bulk query references a field the app's access scopes do not permit (ACCESS_DENIED); malformed bulk query (syntax/schema error); operation timed out on Shopify's side; operation manually canceled; Shopify rate-limited the bulk operation creation.

Common situations: App access scopes changed and no longer include products/orders read; query was edited to reference a field the API key cannot access; large store hitting Shopify bulk operation limits; apiVersion mismatch between schema and query.

Related errors


AI-assisted analysis of gatsbyjs/gatsby@8b06340921 (2026-08-13). Data as JSON: /api/errors/70a3c9c70920016b. Report an issue: GitHub.