dgraph-io/dgraph · error

provided sha does not match query

Error message

provided sha does not match query

What it means

When a persisted-query request includes both the query text and a sha256Hash, the server recomputes the hash of the query via hashMatches; if it doesn't equal the client-supplied hash, the request is rejected with 'provided sha does not match query'. This protects against a client sending a hash that would poison the persisted-query cache.

Source

Thrown at edgraph/graphql.go:97

			PersistedQuery string `json:"dgraph.graphql.p_query"`
		} `json:"me"`
	}

	shaQueryRes := &shaQueryResponse{}
	if len(storedQuery.Json) > 0 {
		if err := json.Unmarshal(storedQuery.Json, shaQueryRes); err != nil {
			return err
		}
	}

	if len(shaQueryRes.Me) == 0 {
		if query == "" {
			return errors.New("PersistedQueryNotFound")
		}
		if match, err := hashMatches(query, sha256Hash); err != nil {
			return err
		} else if !match {
			return errors.New("provided sha does not match query")
		}

		req = &Request{
			req: &api.Request{
				Mutations: []*api.Mutation{
					{
						Set: []*api.NQuad{
							{
								Subject:     "_:a",
								Predicate:   "dgraph.graphql.p_query",
								ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: join}},
							},
							{
								Subject:   "_:a",
								Predicate: "dgraph.type",
								ObjectValue: &api.Value{Val: &api.Value_StrVal{
									StrVal: "dgraph.graphql.persisted_query"}},
							},

View on GitHub (pinned to 759e242be6)

Solutions

  1. Compute the hash as lowercase hex sha256 of the exact query string sent, byte-for-byte
  2. Send the same document object that was hashed (use graphql print/query literal from one source of truth)
  3. Remove the extensions.persistedQuery field and just send the plain query if you don't need persistence
  4. Upgrade/misconfigured client library — align hashing between all client versions

Example fix

// before
const query = 'query Me { me { name } }\n'
const hash = crypto.createHash('md5').update(query.trim()).digest('hex') // wrong algo/input
// after
const crypto = require('crypto')
const hash = crypto.createHash('sha256').update(query, 'utf8').digest('hex')
// send { query, extensions: { persistedQuery: { version: 1, sha256Hash: hash } } }
Defensive patterns

Strategy: validation

Validate before calling

const crypto = require('crypto')
function persistedExtension(query) {
  const sha256Hash = crypto.createHash('sha256').update(query, 'utf8').digest('hex')
  return { version: 1, sha256Hash }
}
// always derive hash from the exact string you send:
gql({ query, extensions: { persistedQuery: persistedExtension(query) } })

Try / catch

try {
  return await gql({ query, extensions: { persistedQuery: { version: 1, sha256Hash: hash } } })
} catch (e) {
  if (/does not match query/.test(e.message)) {
    // fall back to plain query without the persistedQuery extension
    return gql({ query })
  }
  throw e
}

Prevention

When it happens

Trigger: POST with `query` plus extensions.persistedQuery.sha256Hash where sha256(query) != provided hash — e.g. client hashes a different/normalized version of the query (print() output), or uses md5.

Common situations: Apollo client transforming/printing the document before hashing while sending the original string (or vice versa); hand-written clients hashing with wrong algorithm or including trailing whitespace; duplicated persisted-query logic across client versions.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/6f7cb053d543ac3c. Report an issue: GitHub.