← Recipes

阅读中文版本

Debug GraphQL Persisted Query Misses

Resolve persisted-query-not-found errors by checking the APQ hash registry and hash generation between client and server.

~30 mingraphqlapidebugging

Task

Trace automatic persisted query misses by comparing client hashes with the server registry and fixing cache warm-up.

When to use this

Clients intermittently get PersistedQueryNotFound after a deploy, forcing a full query upload and extra latency.

Prerequisites

  • A GraphQL client using automatic persisted queries
  • Server APQ registry access
  • The same query documents the client sends

Steps

1. Reproduce the miss and read the extended error code.

curl -s -X POST https://api.example.com/graphql \
  -H 'content-type: application/json' \
  -d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"deadbeef"}},"operationName":"Feed"}' | jq .errors

Expected output: The response carries PersistedQueryNotFound with the hash the server could not resolve.

2. Verify the client hash matches what the server expects.

import { createHash } from 'node:crypto';
const hash = createHash('sha256').update(queryString).digest('hex');
console.log(hash, hash === expectedHash);

Expected output: A mismatch reveals whitespace or ordering differences between the client query text and the server registry source.

Troubleshooting

Problem

Only new deploys fail

Solution

Warm the server registry during rollout or let clients upload once, then persist.

Problem

Hash differs by formatting

Solution

Hash the exact operation text and normalise trailing whitespace consistently on both sides.

Limitations

  • Registry must be shared across all server instances.
  • Does not fix queries the client never sends.
  • Hash stability depends on exact formatting rules.

What this recipe does

Trace automatic persisted query misses by comparing client hashes with the server registry and fixing cache warm-up.

Clients intermittently get PersistedQueryNotFound after a deploy, forcing a full query upload and extra latency.

Prerequisites

  • A GraphQL client using automatic persisted queries
  • Server APQ registry access
  • The same query documents the client sends

Procedure

  1. Reproduce the miss and read the extended error code.
curl -s -X POST https://api.example.com/graphql \
  -H 'content-type: application/json' \
  -d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"deadbeef"}},"operationName":"Feed"}' | jq .errors

Expected result: The response carries PersistedQueryNotFound with the hash the server could not resolve.

  1. Verify the client hash matches what the server expects.
import { createHash } from 'node:crypto';
const hash = createHash('sha256').update(queryString).digest('hex');
console.log(hash, hash === expectedHash);

Expected result: A mismatch reveals whitespace or ordering differences between the client query text and the server registry source.

How to confirm it worked

Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the client hash exists in the server registry and PersistedQueryNotFound stops appearing after warm-up.

Troubleshooting

Only new deploys fail — Warm the server registry during rollout or let clients upload once, then persist.

Hash differs by formatting — Hash the exact operation text and normalise trailing whitespace consistently on both sides.

Limitations

  • Registry must be shared across all server instances.
  • Does not fix queries the client never sends.
  • Hash stability depends on exact formatting rules.