What “installed” means

What you will be able to do

  1. Name the three checks EasyBuild can run, and say what a pass of each establishes.

  2. Say which of them a given package actually has.

  3. Say what a green build describes, and what it does not.

A build that completes prints a single line and creates one file. That line indicates that the install succeeded. The file is a modulefile and represents the only component that a user ever encounters. Neither of these confirms that the software functions correctly. The distinction constitutes the entirety of this chapter. The gap between them is where bugs reside that reach a user bearing the developer’s name.

Existence is the default claim

After the install step copies files into place, EasyBuild determines if it is counted. The first consulted parameter is sanity_check_paths, and a concrete example follows:.

name = 'VASP6'
version = '6.5.1'

sanity_check_paths = {
    'files': ['bin/vasp_std', 'bin/vasp_gam', 'bin/vasp_ncl'],
    'dirs': [],
}

There are two keys, each being a list of paths relative to the install directory. The check verifies existence. Three files are present, so the build passes.

Consider what it does and does not claim. It asserts that three paths exist. It does not assert that they are executable, that they are the correct version, or that they can start. A zero-byte file at bin/vasp_std would satisfy it.

That is not a criticism of the parameter, which fulfills its single purpose precisely. It cautions that the word “installed” suggests a much stronger claim than the underlying check.

Running something is a stronger claim, and it is optional

sanity_check_commands takes shell commands and runs them:

name = 'archspec'
version = '0.1.0'

sanity_check_commands = [
    "python -c 'from archspec.cpu import host; print(host())'"
]

The check has now actually executed the thing. The import resolved, the interpreter found the package, and the function returned. A zero-byte file fails this check. The code-server easyconfig from An easyconfig is Python lacks such a line, yet its install still runs code-server --help. That command originates from the easyblock. An easyconfig is Python’s point that behavior may be implied by the name and not mentioned in the easyconfig. A package receives an executing check when its easyblock provides one or when its author writes one. Many easyconfigs have neither. For those, “installed” means that the files are present.

A third kind of check, for a failure the first two cannot see

A versionsuffix is part of the name performed the check on the recorded runtime library search path, the RPATH. It reads the installed binaries, resolves the libraries they link against, and reports any that are missing. That catches something the other two checks cannot detect structurally. A file exists. A --help runs. The binary still cannot start. Three checks provide three different claims, increasing in strength: the path is present, something runs, and everything it links resolves. Knowing which of the three a given package satisfies reveals what its green build signifies.

Where the check ran is part of what it proved

The failure mode that consumes the greatest amount of time involves the absence of any parameter. A sanity check runs on the machine that performed the build. On a cluster containing multiple processor architectures, the entire install tree is separated per architecture. A binary tuned for one architecture is not the binary for another. a green check indicates a statement about one of those trees. Here is that arrangement on a real machine:

One module name, and the tree it resolves to

$ hostname
int4.local.snellius.surf.nl

$ readlink -f /sw/arch
/gpfs/admin/_hpc/sw/arch/AMD-ZEN2

$ module load 2025 QMCPACK/4.4.0-foss-2025a

$ echo $EBROOTQMCPACK
/sw/arch/RHEL9/EB_production/2025/software/QMCPACK/4.4.0-foss-2025a

$ qmcpack --version

QMCPACK version 4.4.0 built on Sep  9 2026

Recorded: SURF Snellius, int4 interactive node, 2026-09-09, 2025 module environment

Read the third and fourth commands together. The variable $EBROOTQMCPACK expands to /sw/arch/..., which carries no architecture information. The preceding command shows /sw/arch resolving to the directory AMD-ZEN2. the module text is architecture‑neutral while the target path is architecture‑specific. Loading the same module on a node with a different architecture causes the same string to resolve to a different tree containing a binary compiled for that architecture. The module does not record which tree was loaded. This omission enables the subsequent mistake. A problem was declared fixed after testing on an interactive node. The binary started, resolved its libraries, and ran. The report was incorrect in two respects. The interactive nodes used the architecture shown above, whereas the user’s node did not, causing the check to verify a different tree. That tree was already correct. The lesson extends beyond individual architectures. “It works” is incomplete without specifying where, and on a heterogeneous cluster the location is not a minor detail. Verify on the node type that failed.

EB-Installed-1 — What has this green build established

Three packages install without error on a cluster with two processor architectures. Their easyconfigs differ:

  • alpha declares sanity_check_paths naming three binaries, and nothing else.

  • beta declares the same, plus sanity_check_commands running beta --version.

  • gamma declares the same as beta, and its build had skipsteps = ['sanitycheck'].

For each, say what the green build establishes. Then say what none of the three establishes.

Solution

alpha: three paths exist, on one of the two architectures. Not that they are executable, not that they are the right version, not that they can start. A zero-byte file would have passed.

beta: the same, plus one command ran and returned. That is genuinely stronger, and it is still bounded by what --version touches, which is usually argument parsing and a string.

gamma: that the files were copied. Nothing else. skipsteps removed the step that would have decided whether the install counted, so the green means the build reached the end rather than that anything was verified.

None of the three establishes anything about the other architecture. The install tree is separate per architecture, and a check runs where the build ran. On a heterogeneous cluster “it works” is incomplete until it says where, and the node to verify on is the one that failed.

And how the check is done matters as much as where

The tool invites a related trap. The command patchelf --set-rpath writes DT_RUNPATH, not DT_RPATH. The dynamic linker consults them at different points: DT_RPATH before LD_LIBRARY_PATH, and DT_RUNPATH after it. Patching a binary in that way and testing it in a shell that already has the library on LD_LIBRARY_PATH causes resolution to go through the environment. No report about the patch is produced. That test can pass while only demonstrating that the shell already contained the library. Test in a manner that the environment cannot answer for. LD_PRELOAD handles a single library; otherwise read the resolution instead of the exit code. A test that can pass for a reason other than the one being tested does not qualify as a test. That matches the shape of A versionsuffix is part of the name’s stale name: an accurate observation carrying a wrong inference.

The modulefile is the deliverable

Chapters 1 through 5 collectively produce a single text file. The parameters of the easyconfig, the derivations of the easyblock, the toolchain hierarchy, the pinned dependency versions, and the suffix incorporated into the name all become a modulefile. The generated modulefile sets environment variables and declares prerequisites. Running module load provides the variables without exposing the underlying details. Two consequences arise, both of which have already been observed. The modulefile is generated rather than manually written, causing it to inherit every inference presented in this book. The expression moduleclass = 'tools' in the sample from An easyconfig is Python represents a modulefile concern that propagated to the easyconfig. The modulefile may be entirely correct while describing an incorrect dependency tree. This situation corresponds precisely to A versionsuffix is part of the name. A module labeled CUDA 12.9.1, declaring a dependency on CUDA 12.9.1, both accurate, resolves into a 13.0 tree. The modulefile contains no false information, yet the module remains unusable.

The three checks, side by side

Check

What a pass establishes

What it misses

sanity_check_paths

These paths exist

Whether they are executable, the right version, or able to start. A zero-byte file passes.

sanity_check_commands

Something ran and returned

Anything the command does not reach. --help never touches the code that computes.

The runtime library search path

Everything the binaries link resolves

Whether the program is correct, and whether the same is true on another node

A package has the first. It has the second only if its author or its easyblock supplied one. Knowing which of the three a given package carries is knowing what its green build means. Then the two things that are not about parameters at all. A pass describes the tree it ran against. On a cluster with more than one architecture it names one of them. Verify on the node type that failed, however far away it is. And patchelf --set-rpath writes DT_RUNPATH, which the dynamic linker consults after LD_LIBRARY_PATH. A test your own shell can satisfy is not a test. The modulefile at the end of all this is the only artifact a user meets. It is generated from every inference in this book, and it can be accurate about a tree that does not work.