Environment Variables

This page lists every HORUS_* variable HORUS reads, and every one it sets for a process it starts — a node under horus launch, a Python node under horus run, an exec driver, a CLI plugin. The ones you would set deliberately come first, grouped by area; the internal and test-only ones are at the bottom so you can recognise one if you see it.

There is deliberately no count in this sentence: the last one said 47 while the tables listed 53. Completeness is enforced instead by docs_contract::every_environment_variable_is_in_the_reference_page, which fails the build when the source reads or sets a name this page does not list.

Logging

VariableDefaultEffect
HORUS_LOG_FILEunset1 or true starts the file drain. Without it nothing is written to disk and the other HORUS_LOG_* variables have no effect
HORUS_LOG_DIR.horus/logsDirectory for rotated log files
HORUS_LOG_MAX_SIZE10485760 (10 MB)Rotate a log file once it exceeds this many bytes
HORUS_LOG_MAX_FILES5How many rotated files to keep
HORUS_LOG_BUFFER_SIZE5000Entries held in the in-memory ring the monitor reads. Clamped to 100–50000 — a value outside that range is silently adjusted, not rejected
HORUS_ERROR_BUFFER_SIZE500Entries in the dedicated error ring, which survives a flood of ordinary logs
HORUS_REMOTE_LOG_SIZE2000Entries buffered for replication to other machines

The error buffer is separate on purpose: a node logging at high rate can evict its own errors from the main buffer, and the error ring is what horus blackbox reads after a crash.

Networking

Only meaningful with the net capability enabled — see Configuration.

VariableDefaultEffect
HORUS_NET_ENABLEDenabledOpt-out only. Once the binary is built with the net capability, replication is on and auto-starts at scheduler.run(); 0 or false turns it off. Any other value, including true, leaves it on
HORUS_NO_NETWORKunsetSet to disable replication for one run — wins over the above
HORUS_NET_PORT9100UDP port the replicator binds
HORUS_NET_MULTICAST224.0.69.72Discovery multicast group
HORUS_NET_PEERemptyComma-separated peers to unicast directly, skipping multicast discovery
HORUS_NET_ALLOW_PEERSloopback + RFC1918 + link-local + CGNATWhich source addresses inbound datagrams may come from. any/* accepts the public internet
HORUS_NET_SECRETunsetShared token peers must match. Used verbatim; there is no env: indirection in horus.toml
HORUS_NET_IMPORTautoWhich remote topics to subscribe to
HORUS_NET_DENY_EXPORTemptyTopics that must never leave this machine
HORUS_NET_OPTIMIZERSemptyComma-separated: fusion, delta, spatial, predict. An unknown name warns and installs nothing
HORUS_NET_HEARTBEAT_MS50 (200 under WSL)How often peers announce themselves
HORUS_NET_MISSED_THRESHOLD3 (5 under WSL)Missed heartbeats before the link counts as lost
HORUS_NET_ON_LINK_LOSTwarnAction on link loss: warn, safe_state (alias safestate), stop. Anything else silently means warn
HORUS_NET_DISCOVERY_MISSES3, or higher under WSL2Missed announcements before a peer is considered gone
HORUS_NET_LOG_LEVELwarnWhich logs replicate to peers: none/off, error, warn/warning, info, debug/trace/all

Safety

VariableDefaultEffect
HORUS_ESTOP_REMOTEwarnWhat a remote emergency stop does locally. off, ignore, 0 or false disable it; anything else warns
HORUS_ESTOP_KEYunsetHMAC-SHA256 key authenticating the networked e-stop. 64 hex chars are used as raw key material; anything else is stretched with PBKDF2-HMAC-SHA256. While unset, remote e-stops are rejected — see Authenticating the networked e-stop

Note the default: an unrecognised value warns rather than disabling, so a typo cannot silently switch off remote e-stop.

Runtime

VariableDefaultEffect
HORUS_SIM_MODEunset1 runs drivers in simulation. Set by horus run --sim
HORUS_SIM_TARGETSunsetComma-separated driver names to simulate, when only some should be
HORUS_DRIVERSunsetComma-separated driver list. Set by horus run --drivers
HORUS_ENABLEunsetComma-separated capabilities, e.g. net. Set by horus run --net
HORUS_TELEMETRY_ENDPOINTunsetWhere the scheduler exports telemetry
HORUS_NAMESPACEdefaultIsolates shared memory. Two processes with different namespaces cannot see each other's topics — the equivalent of a ROS 2 domain ID. Read once per process and cached, so changing it mid-run has no effect
HORUS_NODE_NAMEbinary nameNames the scheduler in horus node list and in presence files
HORUS_RT_CORESunsetComma-separated core IDs to pin real-time threads to, e.g. 2,3. Ignored when the scheduler already has .cores(&[..]), so it is an override for deployment rather than a default
HORUS_RECORD_SESSIONunsetA non-empty value enables recording for the run. Set by horus run --record <session>; setting it by hand has the same effect

Parameters

HORUS_PARAM_<NAME> sets the runtime parameter <name>, lowercased:

HORUS_PARAM_MAX_SPEED=2.5 HORUS_PARAM_DEBUG=true horus run

reaches your node as self.params.get::<f64>("max_speed") and get::<bool>("debug").

VariableDefaultEffect
HORUS_PARAM_<NAME>unsetSets parameter <name> (lowercased). Values parse as bool, integer, float, or string, in that order

This is the highest-precedence layer: it beats .horus/config/params.yaml, which beats the built-in defaults. horus launch uses it to pass per-node parameters, so a variable set by hand overrides a launch file for that run.

CLI and toolchain

VariableDefaultEffect
HORUS_ASSUME_YESunset1 answers horus new and horus setup-rt prompts with yes. horus new -y/--yes sets it for you; horus setup-rt has no such flag, so the variable is the only way to accept its kernel-install and limits-file prompts without a terminal
HORUS_AUTO_INSTALLunset1 installs missing C++ dependencies without asking
HORUS_CPP_LINKstaticHow the HORUS C++ runtime is linked into a node binary: static or shared. Static puts the whole runtime in every binary — about 2.0 MB stripped per node in release, 78 MB in debug — so a robot running a dozen nodes pays it a dozen times. shared links libhorus_cpp.so and leaves each node at tens of kilobytes, at the cost of shipping the library beside them. Overrides [cpp] link in horus.toml for one build
HORUS_VERBOSEunset1 shows the full compiler and CMake output
HORUS_SOURCE_DIRunsetPath to a HORUS checkout, used to resolve message types and sources
HORUS_REGISTRY_URLhttps://api.horusrobotics.devPackage registry, for self-hosting or offline mirrors
HORUS_PLUGIN_REGISTRY_URLhttps://plugins.horusrobotics.dev/api/v1Plugin registry
HORUS_TRUST_PROXYunsetTrust an HTTP proxy's forwarding headers
HORUS_ALLOW_LOCAL_PLUGINSfalseAllow horus monitor to run the project-local .horus/bin/horus-monitor. Off by default — that binary is not trust-checked, and cloning a repo must not be enough to run its code
HORUS_NO_SHELL_INTEGRATIONunsetSet during install to skip the cargo/pip/cmake shell proxies
HORUS_BUILD_FROM_SOURCEunset1 makes install.sh skip the pre-built binary and compile from a checkout instead. The installer names it in two of its own error messages — "build from source with HORUS_BUILD_FROM_SOURCE=1" when neither sha256sum nor shasum is available to verify the download, and again when the download fails — so it is the documented escape hatch from a failed install
HORUS_INSTALL_BRANCHmainBranch install.sh clones when it builds from source
HORUS_NETunset1 or true reports networking as enabled. horus doctor prints "use --net or HORUS_NET=1 to enable". Setting it by hand only changes what doctor reports: horus run --net sets it and adds net to the capability list, and it is the capability list that reaches cargo --features. On its own this variable does not build a networked binary
HORUS_SOURCEunsetPath to a HORUS checkout, used to resolve the framework itself when horus run builds a single Rust file. Distinct from HORUS_SOURCE_DIR, which resolves message definitions — a name that is a prefix of another is exactly the kind of pair a substring check treats as one variable, and this one went undocumented behind HORUS_SOURCE_DIR for that reason. Ignored unless the path contains horus/Cargo.toml

Set by HORUS for the processes it starts

You do not set these; HORUS puts them into the environment of a process it spawns, so they are readable from your node, driver or plugin. They are listed because they are a contract: your code may read them, and a release that renamed one would break that code.

VariableSet byValue
HORUS_NODE_NAMEhorus launch (per node)The node's name from the launch file. Also read by the scheduler to name itself in horus node list
HORUS_NODE_FILEhorus run <file>.pyAbsolute path to the Python file being run. The generated wrapper reads it with os.environ.get('HORUS_NODE_FILE') and exits with an error if it is missing, so it is the contract behind every horus run of a Python node
HORUS_DRIVER_NAMEevery exec driverThe driver's name from horus.toml. Injected alongside HORUS_PARAM_<KEY> for each of the driver's parameters, so an exec driver written in any language can tell which driver instance it is
HORUS_PLUGINevery CLI pluginAlways 1. A plugin can use it to tell "invoked by horus" from "invoked directly by a user"
HORUS_VERSIONevery CLI pluginThe horus version that launched the plugin, for compatibility checks

Set but not yet read

These are put into the child's environment and nothing in HORUS reads them back. They are documented rather than hidden because they are visible to your code and because their emptiness is the answer to "why did setting this in my launch file do nothing?".

VariableSet byStatus
HORUS_NODE_RATE_HZhorus launch, when a node declares rate_hzNothing reads it. A node's rate comes from its own .rate() call; the launch file value is passed along but does not override it
HORUS_NODE_PRIORITYhorus launch, when a node declares priorityNothing reads it. The source says so at the assignment: kept only so a configured value leaves a trace
HORUS_NODE_NAMESPACEhorus launch, when a node declares namespaceNothing reads it. Shared-memory isolation is keyed off HORUS_NAMESPACE, which horus launch sets from the launch file's top-level namespace — so a per-node namespace: does not isolate that node
HORUS_INJECT_SESSIONhorus record inject --scriptThe session name, passed to the horus run child that executes the script. Nothing reads it
HORUS_INJECT_NODEShorus record inject --scriptComma-separated injected node names, or * for --all. Nothing reads it

Internal and test-only

Set by HORUS itself or by its own test suite. Listed so you can recognise them, not so you can set them.

HORUS_CI_DURATION_MS, HORUS_CI_READY_FILE, HORUS_CI_TOPIC_NAME, HORUS_CI_BEST_EFFORT, HORUS_CI_TOPIC_IN_CTOR, HORUS_AUTH_ALLOW_MISSING_STATE, HORUS_ESTOP_STDERR_CHILD, HORUS_TEST_BUILD_EXAMPLES, HORUS_DOCS_DIR.

(HORUS_INSTALL_BRANCH used to be listed here. It is set by the person running install.sh, not by HORUS, so it belongs in the table above with the rest of the installer's knobs.)

HORUS_CI_BEST_EFFORT and HORUS_CI_TOPIC_IN_CTOR shape the test node used by the introspection contract tests. The first omits .rate(), so the node stays on the main thread instead of moving to the RT executor; the second builds its Topic in the constructor rather than in init(). Both distinctions matter because the two arrangements report their metrics through different mechanisms and have failed independently of each other.

HORUS_DOCS_DIR points the documentation contract tests at a horus-docs checkout, and HORUS_TEST_BUILD_EXAMPLES=1 opts into the slow example-build test. Both matter only if you are working on HORUS itself.