All variable values are strings, no other types exist
Uninitialized variables (let x) default to empty string ""
Variables are scoped to their enclosing block (test, shell, fn, effect)
Inner blocks can shadow outer variables with a new let declaration
Reassignment (x = expr) mutates an existing variable from an outer scope
Environment variables from the host process are available as pre-set variables in all scopes (read-only — let creates a shadow, not a modification of the process environment)
Regex capture groups ($1, $2, …) are set after a <? match and remain in scope until overwritten by the next <?
Effect names must start with an uppercase letter (CamelCase) — this is enforced at the syntactic level, disambiguating effects from functions in imports
An effect is a reusable setup procedure that produces running shells
An effect has three explicit interface components:
expect — declares required environment variables the effect reads; the resolver validates these are satisfiable
expose — declares which shells the effect makes available to callers; internal shells not listed in expose are terminated after setup
start — declares dependency effects with optional env remapping via overlay
None of these declarations are mandatory: an effect may have no expect, no start, and no expose
start Effect runs the dependency for side effects only — its shells are not accessible
start Effect as alias runs the dependency and makes its exposed shells available via dot-access (shell alias.shell_name)
start Effect as alias { KEY = expr } provides an overlay that remaps the caller’s environment into the dependency’s environment
The shorthand form KEY (without = expr) is equivalent to KEY = KEY
Effects inherit the full parent environment — overlay entries override specific keys
Effect instance identity is determined by (effect-name, evaluated overlay restricted to expect-declared vars):
Same identity tuple = same instance (deduplicated, reused)
Different identity tuple = separate instances
When a test or effect starts the same effect multiple times with the same evaluated overlay, only one instance is created
Exposed shells are accessed via dot notation: shell alias.shell_name { ... }
For composed effects, expose can re-export a dependency’s shell: expose dep.shell as public_name
Effects run before the test body; the dependency graph is resolved and executed in topological order
Circular effect dependencies are a parse error
If an effect fails (a match times out during setup), all tests depending on it are failed
Each effect has an optional cleanup block that runs when the effect is torn down
Condition markers are placed immediately before test, effect, fn, or pure fn declarations
Condition markers evaluate before any shells are spawned
Test-level markers are checked before execute_effects
Effect-level markers are checked before the effect’s shells are created
Function-level markers are checked during resolution; a skipped function causes all tests that call it to be skipped
A bare marker (kind only, no modifier) is unconditional:
# skip always skips, # flaky always marks flaky, # run is a no-op
A conditional marker requires a modifier (if/unless) and an expression
Expressions are quoted strings with ${VAR} interpolation or bare numbers:
"${CI}" — environment variable reference
"literal" — literal string
"${HOST}:${PORT}" — compound interpolation
42 — bare number (compared as string)
Bare variable identifiers (e.g. CI) are valid in markers
Expression evaluation uses ENV-only lookup (Arc<Env>) — no frame variables or test-scope variables exist at evaluation time
Truthiness: empty string or unset variable is false, any non-empty string is true
= operator: evaluates both sides, returns the LHS value if LHS equals RHS, empty string otherwise
? operator: evaluates LHS, compiles the regex pattern (with ${var} interpolation), returns the match if found, empty string otherwise
Modifier semantics:
if acts when the result is truthy
unless acts when the result is falsy
Kind semantics:
skip: skips the test/effect when the condition is met
run: skips the test/effect when the condition is NOT met (inverse of skip)
flaky: marks the test as flaky — with [flaky].max_retries > 0 in Relux.toml, a failing flaky test is retried from scratch with exponentially increasing tolerance timeouts (base × m^(retry-1)). With max_retries = 0 (default), the marker is documentary only
Multiple markers stack with AND semantics: all conditions must pass or the test is skipped
When an effect is skipped, all tests depending on it are also skipped
When a function is skipped, all tests that call it are also skipped