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

  1. Set Op to a valid VarOpType (set, cas, delete, delete-cas, lock-acquire, lock-release)
  2. Ensure client and server Nomad versions match if using newly added operations
  3. Validate the op value in client code before calling Apply
  4. 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

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


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/d82d94e75a74c8d9. Report an issue: GitHub.