ory/hydra · error
unsupported operation: %s
Error message
unsupported operation: %s
What it means
ApplyJSONPatch in oryx/jsonx applies a JSON Patch but only allows the operations add, remove, and replace (see opAllowList). Other RFC 6902 operations — copy, move, and test — are rejected because the underlying evanphx/json-patch implementation has known bugs with them (see github.com/evanphx/json-patch/pull/158). The error names the offending operation kind.
Source
Thrown at oryx/jsonx/patch.go:65
// is invalid or if the patch includes paths that are denied. denyPaths is a
// list of path globs (interpreted with [glob.Compile] that are not allowed to
// be patched.
func ApplyJSONPatch[T any](p json.RawMessage, object T, denyPaths ...string) (result T, err error) {
patch, err := jsonpatch.DecodePatch(p)
if err != nil {
return result, errors.WithStack(err)
}
denyPattern := fmt.Sprintf("{%s}", strings.ToLower(strings.Join(denyPaths, ",")))
matcher, err := glob.Compile(denyPattern, '/')
if err != nil {
return result, errors.WithStack(err)
}
for _, op := range patch {
// Some operations are buggy, see https://github.com/evanphx/json-patch/pull/158
if isUnsupported(op) {
return result, errors.Errorf("unsupported operation: %s", op.Kind())
}
path, err := op.Path()
if err != nil {
return result, errors.Errorf("error parsing patch operations: %v", err)
}
if matcher.Match(strings.ToLower(path)) {
return result, errors.Errorf("patch includes denied path: %s", path)
}
// JSON patch officially rejects replacing paths that don't exist, but we want to be more tolerant.
// Therefore, we will ensure that all paths that we want to replace exist in the original document.
if op.Kind() == "replace" && !isElementAccess(path) {
op["op"] = new(json.RawMessage(`"add"`))
}
}
original, err := json.Marshal(object)
if err != nil {View on GitHub (pinned to 4174065ffb)
Solutions
- Rewrite the patch using only add, remove, and replace: express move as remove+add, copy as read+add, and drop test preconditions.
- Split the patch: perform test/copy/move client-side and send only the resulting add/remove/replace operations.
- If you need copy/move server-side, apply the patch with jsonpatch directly (bypassing ApplyJSONPatch) after validating paths yourself.
Example fix
// before
[{"op": "move", "from": "/a", "path": "/b"}]
// after
[{"op": "add", "path": "/b", "value": <value of /a>}, {"op": "remove", "path": "/a"}] Defensive patterns
Strategy: validation
Validate before calling
allowed := map[string]bool{"add": true, "remove": true, "replace": true}
for _, op := range patchOps {
if !allowed[op["op"].(string)] {
return fmt.Errorf("operation %v not allowed; use add/remove/replace", op["op"])
}
} Prevention
- Express move/copy/test as add/remove/replace before sending.
- Validate patches against the RFC 6902 schema minus copy/move/test.
- Document the supported operation subset for API consumers.
When it happens
Trigger: Calling ApplyJSONPatch with a patch document containing any operation whose "op" is "copy", "move", or "test" (anything not in {add, remove, replace}); e.g., via patchOAuth2Client receiving a user-supplied PATCH body.
Common situations: API clients crafting RFC 6902 patches that include 'test' preconditions or 'move'/'copy' for convenience; generic JSON Patch tooling that produces all six standard operations.
Related errors
- error parsing patch operations: %v
- patch includes denied path: %s
- Token is expired
- Token used before issued
- Token is not valid yet
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/47d293d44ebe295a.
Report an issue: GitHub.