Using ZedTokens and Consistency Levels in SpiceDB to Prevent the New Enemy Problem
Learn how to capture SpiceDB zedtokens from writes and use the at_least_as_fresh consistency level to guarantee that permission checks see the latest changes, avoiding stale reads after updates.
17 Sept 2026, 21:35 UTC

Problem Statement
In a replicated SpiceDB deployment, a write that grants or revokes a relationship may not be immediately visible on every replica. If a subsequent CheckPermission uses the default minimize_latency consistency, the request can be served from a stale replica and return a wrong result – the classic New Enemy Problem. The goal of this guide is to show how to capture the zedtoken returned by a write and use it with the at_least_as_fresh consistency option to guarantee that the check sees the write or a newer one.
Prerequisites
- SpiceDB 1.4.x or newer, running in a replicated environment (at least two replicas).
- Access to the SpiceDB gRPC or REST API (or the
zedCLI). - Basic knowledge of SpiceDB schemas, objects, and relationships.
- Permissions to create relationships and perform permission checks.
Step‑by‑Step Procedure
- Define a simple schema (if not already present). For example, grant a user read access to a document:
type document relations { reader: [user] } permission read = document#reader - Create the relationship and capture the zedtoken. Using the
zedCLI:zed write-relationships \n --relationship "user:alice@document:doc1#reader" \n --commit-message "Grant read to Alice"The command outputs a JSON object containing a
zedtokenfield. Store that token for later use. Example (placeholder):{ "zedtoken": "v1:abcd1234efgh5678" } - Perform a permission check with
at_least_as_fresh. Pass the captured token in the consistency options. Using gRPC viazedCLI:zed check-permission \n --subject "user:alice" \n --permission "read" \n --resource "document:doc1" \n --consistency "at_least_as_fresh" \n --zedtoken "v1:abcd1234efgh5678"Expected result:
ALLOW(orDENYif the relationship had been revoked). If the token is stale (garbage‑collected), the API will return aFAILED_PRECONDITIONerror; see the fallback section. - Validate the necessity of the token. Immediately after the write, run the same check with
minimize_latency:zed check-permission \n --subject "user:alice" \n --permission "read" \n --resource "document:doc1" \n --consistency "minimize_latency"On a replicated cluster, you may observe
DENYif the request hits a replica that hasn't yet applied the write, illustrating the New Enemy Problem.
Consistency Options Table
| Option | Latency | Staleness | Typical Use |
|---|---|---|---|
| minimize_latency | Lowest | May be stale | High‑traffic, non‑security‑critical reads |
| at_least_as_fresh (with zedtoken) | Medium | Guarantees ≥ token revision | After a write that changes access for a user/session |
| fully_consistent | Highest | Always latest | Security‑critical paths, fallback when token invalid |
Fallback Strategy When a ZedToken Is Expired
Zedtokens are not permanent. SpiceDB garbage‑collects old revisions after a configurable window. If an at_least_as_fresh check fails with FAILED_PRECONDITION, fall back to fully_consistent to guarantee correctness:
zed check-permission \n --subject "user:alice" \n --permission "read" \n --resource "document:doc1" \n --consistency "fully_consistent"
Afterward, capture the new zedtoken from the response (if the API returns one) and store it for future checks.
Storing ZedTokens in Application State
A common pattern is to persist the most recent zedtoken per user session or per resource row. For example, in a web app, add a last_zedtoken field to the session cookie or database record. Each request then supplies that token in the at_least_as_fresh consistency option, ensuring that the check sees all writes issued during the session without requiring fully_consistent on every call.
Common Pitfalls & Mitigations
- Using the wrong consistency name: Verify the exact enum names in your SpiceDB client library. The CLI uses strings, but gRPC libraries use enum values.
- Assuming tokens are forever valid: Store the GC window in your application and refresh tokens periodically.
- Overusing fully_consistent: Reserve it for paths where correctness outweighs latency. In high‑traffic APIs, keep the default
minimize_latencyand only upgrade when a recent write has occurred. - Missing the zedtoken in the check request: The API will reject the request with a
INVALID_ARGUMENTerror. Always include the token when usingat_least_as_fresh.
Practical Verification Checklist
- After a write, run
zed list-relationshipsto confirm the relation exists. - Run
zed check-permissionwithat_least_as_freshand the captured token; verify the result matches expectations. - Simulate a stale replica by delaying replication (e.g., using a network partition test) and confirm that
minimize_latencycan return stale results. - Force a token expiry by waiting beyond the GC window and ensure the fallback to
fully_consistentsucceeds.
Summary
By capturing the zedtoken from a relationship write and passing it with at_least_as_fresh on subsequent permission checks, you guarantee that the check sees the write or a newer revision, eliminating the New Enemy Problem. Store the token per session or resource to keep checks fast, and fall back to fully_consistent only when the token is no longer usable. This pattern balances correctness, latency, and load in distributed SpiceDB deployments.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.