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
- Compute the hash as lowercase hex sha256 of the exact query string sent, byte-for-byte
- Send the same document object that was hashed (use graphql print/query literal from one source of truth)
- Remove the extensions.persistedQuery field and just send the plain query if you don't need persistence
- 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
- Hash and send the identical query string from one source (never normalize one and send the other)
- Use sha256 hex, not md5 or base64
- Pin and align persisted-query client library versions
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
- PersistedQueryNotFound
- same sha returned %d queries
- Cycle detected: %s
- Missing fragment: %s
- Unavailable: Server not ready.
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/6f7cb053d543ac3c.
Report an issue: GitHub.