hashicorp/nomad · error
svPreApply: unexpected VarOp received: %q
Error message
svPreApply: unexpected VarOp received: %q
What it means
hasOperationPermissions maps each VarOp to the ACL capability required (list/write/destroy). If Apply receives an op outside the known set, no ACL case matches and the code returns this internal inconsistency error. It means an unrecognized VarOp reached permission checking — a programming or wire-format issue, not an operator action.
Source
Thrown at nomad/variables_endpoint.go:191
hasPerm := func(perm string) bool {
return aclObj.AllowVariableOperation(namespace,
path, perm, nil)
}
switch op {
case structs.VarOpSet, structs.VarOpCAS, structs.VarOpLockAcquire,
structs.VarOpLockRelease:
if !hasPerm(acl.VariablesCapabilityWrite) {
return structs.ErrPermissionDenied
}
case structs.VarOpDelete, structs.VarOpDeleteCAS:
if !hasPerm(acl.VariablesCapabilityDestroy) {
return structs.ErrPermissionDenied
}
default:
return fmt.Errorf("svPreApply: unexpected VarOp received: %q", op)
}
return nil
}
func canonicalizeAndValidate(args *structs.VariablesApplyRequest) error {
switch args.Op {
case structs.VarOpLockAcquire:
// In case the user wants to use the default values so no lock data was provided.
if args.Var.VariableMetadata.Lock == nil {
args.Var.VariableMetadata.Lock = &structs.VariableLock{}
}
args.Var.Canonicalize()
err := args.Var.ValidateForLock()
if err != nil {View on GitHub (pinned to 482b49bf1a)
Solutions
- Set Op to a valid VarOpType (set, cas, delete, delete-cas, lock-acquire, lock-release)
- Ensure client and server Nomad versions match if using newly added operations
- Validate the op value in client code before calling Apply
- Update the Nomad server if a newer client introduces unknown ops
Example fix
// before
req := &api.VariablesApplyRequest{Op: api.VarOpType("upsert"), Var: v}
// after
req := &api.VariablesApplyRequest{Op: api.VarOpSet, Var: v} Defensive patterns
Strategy: validation
Validate before calling
switch req.Op {
case api.VarOpSet, api.VarOpCAS, api.VarOpDelete, api.VarOpDeleteCAS, api.VarOpLockAcquire, api.VarOpLockRelease:
// ok
default:
return fmt.Errorf("unsupported VarOp %q", req.Op)
} Type guard
func isValidVarOp(op api.VarOpType) bool {
switch op {
case api.VarOpSet, api.VarOpCAS, api.VarOpDelete, api.VarOpDeleteCAS, api.VarOpLockAcquire, api.VarOpLockRelease:
return true
}
return false
} Try / catch
_, err := client.Variables().Apply(req, nil)
if err != nil && strings.Contains(err.Error(), "unexpected VarOp") {
// fix op value or align client/server versions
} Prevention
- Only use named VarOp constants; never cast raw strings
- Keep client and server Nomad versions in sync for new ops
- Validate Op server-side expectations in SDK wrappers
When it happens
Trigger: Calling Variables Apply with an Op value that is not VarOpSet/VarOpCAS/VarOpDelete/VarOpDeleteCAS/VarOpLockAcquire/VarOpLockRelease — e.g. an invalid string deserialized into VarOpType, or a newer client op against an older server.
Common situations: Hand-crafted API requests with a misspelled op; version skew where a new operation type is sent to an older server; SDK misuse passing an empty Op.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- token name too long
- client token missing policies or roles
- management token cannot be associated with policies or roles
- token type must be client or management
- expiration time cannot be before create time
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/d82d94e75a74c8d9.
Report an issue: GitHub.