Troubleshooting (Python)
Fix installation failures, runtime errors, performance problems, and debug HORUS Python applications. Start with the quick diagnostic steps, then jump to the section matching your symptom.
Prerequisites
- HORUS installed (Installation Guide)
- Python 3.9+ (
python3 --version) - Access to a terminal in your project directory
Common Errors At a Glance
| Error | Jump To | Quick Fix |
|---|---|---|
ModuleNotFoundError: No module named 'horus' | Import Errors | pip install horus-robotics |
ModuleNotFoundError: No module named '_horus' | Native Extension Missing | Rebuild with maturin develop --release |
Failed to create Topic | Topic Creation Errors | horus clean --shm |
Permission denied on /dev/shm | SHM Permission Denied | Fix /dev/shm permissions |
recv() always returns None | Topic Not Found | Check topic name match |
| Topics not visible across processes | Multi-Process Issues | Use horus run, not python directly |
High dropped_count | Messages Dropped | Keep messages under slot size |
tick() taking too long | GIL Blocking | Release GIL with compute=True |
TypeError in tick function | Wrong Tick Signature | Use def tick(node): |
| Version mismatch (CLI vs Python) | Version Mismatch | Reinstall both from same source |
horus: command not found | CLI Not Found | Add ~/.cargo/bin to PATH |
Quick Diagnostic Steps
When your HORUS Python application is not working:
- Verify imports:
python3 -c "import horus; print(horus.__version__)" - Check the Monitor: Run
horus monitorin a second terminal to see active nodes and topics - Verify Topics: Ensure publisher and subscriber use the exact same topic names
- Check shared memory: Run
horus clean --shmto remove stale regions from a previous crash - Test individually: Run nodes one at a time to isolate the problem
- Check CLI health: Run
horus doctorto diagnose system-level issues
Installation Issues
ModuleNotFoundError: No module named 'horus'
Symptom:
>>> import horus
ModuleNotFoundError: No module named 'horus'
Cause: The horus Python package is not installed in your active Python environment.
Fix:
# Install from PyPI
pip install horus-robotics
# Verify
python3 -c "import horus; print('OK')"
If you are building from source:
cd horus_py
maturin develop --release
Still failing? Check which Python you are using:
which python3
python3 -c "import sys; print(sys.executable)"
pip show horus-robotics
If pip show says it is installed but import horus fails, you are likely running a different Python than the one pip installed into. See Virtual Environment Issues below.
ModuleNotFoundError: No module named '_horus'
Symptom:
>>> import horus
ModuleNotFoundError: No module named '_horus'
Cause: The Python wrapper (horus/__init__.py) loads correctly, but the native Rust extension (_horus) is missing or was compiled for a different Python version.
Fix:
# Rebuild the native extension
cd horus_py
maturin develop --release
# Verify the .so file exists
python3 -c "import _horus; print('Native extension OK')"
Common reasons the native extension is missing:
- You installed
horusfrom a wheel built for a different Python version (e.g., built for 3.11, running 3.12) - The
.sofile was compiled for a different platform - You reinstalled Python without rebuilding the extension
If maturin fails:
# Ensure Rust is installed
rustup --version
# Ensure maturin is installed
pip install maturin
# or: cargo install maturin
# Rebuild
cd horus_py
maturin develop --release
pip install horus-robotics Fails
Symptom:
error: can't find Rust compiler
or:
error: command 'rustc' failed
Cause: The horus-robotics package contains Rust code compiled via PyO3. If no pre-built wheel exists for your platform, pip tries to build from source and needs a Rust toolchain.
Fix:
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Retry
pip install horus-robotics
On Ubuntu/Debian, you may also need build tools:
sudo apt update
sudo apt install -y build-essential pkg-config libssl-dev
pip install horus-robotics
Version Mismatch Between CLI and Python Bindings
Symptom: Strange behavior, missing features, or errors like:
RuntimeError: Scheduler version mismatch
or topics created by Python nodes are not visible to Rust nodes (or vice versa).
Cause: The horus CLI and the Python bindings were built from different source versions. They must share the same horus_core version to communicate via shared memory.
Fix:
# Check versions
horus --version
python3 -c "import horus; print(horus.__version__)"
# If they differ, rebuild both from the same source
cd /path/to/horus
./install.sh # Rebuilds CLI
cd horus_py
maturin develop --release # Rebuilds Python bindings
Runtime Issues
horus: command not found
Symptom:
$ horus run
bash: horus: command not found
Fix:
# Add to PATH
export PATH="$HOME/.cargo/bin:$PATH"
# Make permanent
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Verify
horus --help
Topic Creation Errors
Symptom:
RuntimeError: Failed to create Topic
Common causes and fixes:
Stale shared memory from a previous crash:
horus clean --shm
Insufficient permissions on shared memory (Linux):
horus doctor # Diagnose permission issues
horus clean --shm # Clean stale regions
Conflicting topic names (same name, different types):
# BAD: Same topic name used with different data shapes
node_a = horus.Node(pubs=["data"], tick=lambda n: n.send("data", 42.0))
node_b = horus.Node(pubs=["data"], tick=lambda n: n.send("data", {"x": 1}))
# GOOD: Distinct names for distinct data
node_a = horus.Node(pubs=["sensor.raw"], tick=lambda n: n.send("sensor.raw", 42.0))
node_b = horus.Node(pubs=["sensor.processed"], tick=lambda n: n.send("sensor.processed", {"x": 1}))
"No such file or directory" When Creating Topic
Symptom:
RuntimeError: Failed to create publisher 'camera': No such file or directory
Cause: You are using slashes (/) in a topic name. On macOS, shared memory uses shm_open() which does not support slashes. On Linux it works, but your code will not be portable.
Fix: Use dots instead of slashes:
# WRONG - fails on macOS
node = horus.Node(pubs=["sensors/camera"], tick=tick)
# CORRECT - works on all platforms
node = horus.Node(pubs=["sensors.camera"], tick=tick)
Permission Denied on /dev/shm
Symptom:
PermissionError: [Errno 13] Permission denied: '/dev/shm/horus/...'
Cause: The shared memory directory has restrictive permissions, or a previous process created SHM files owned by a different user (e.g., root).
Fix:
# Diagnose
horus doctor
# Clean stale shared memory (safe — only removes HORUS regions)
horus clean --shm
# If /dev/shm itself is restricted (rare)
ls -la /dev/shm/
# Should show drwxrwxrwt (world-writable with sticky bit)
Never manually delete files inside /dev/shm/ with rm -rf. Use horus clean --shm instead — it only removes HORUS-owned regions and avoids breaking other applications.
Topic Not Found / recv() Always Returns None
Symptom: Your subscriber node never receives messages even though the publisher is running.
def tick(node):
msg = node.recv("sensor_data")
if msg is not None:
print(f"Got: {msg}") # Never prints
Common causes:
Topic name mismatch (the #1 cause):
# Publisher
pub = horus.Node(pubs=["sensor_data"], tick=pub_tick)
# Subscriber (TYPO — missing underscore)
sub = horus.Node(subs=["sensordata"], tick=sub_tick)
# CORRECT:
sub = horus.Node(subs=["sensor_data"], tick=sub_tick)
Debug with CLI tools:
# List all active topics
horus topic list
# Watch messages on a specific topic
horus topic echo sensor_data
# Check topic publish rate
horus topic hz sensor_data
Publisher has not sent yet:
# This is normal on the first few ticks — recv() returns None
# until the publisher sends its first message
def tick(node):
msg = node.recv("sensor_data")
if msg is not None:
process(msg)
# else: no message yet — just continue
Wrong execution order:
# Publisher should run first (lower order number)
pub = horus.Node(pubs=["data"], tick=pub_tick, order=0)
# Subscriber runs after
sub = horus.Node(subs=["data"], tick=sub_tick, order=1)
Topic not declared in subs/pubs:
# BAD: "temperature" not declared in subs
node = horus.Node(subs=["temp"], tick=tick)
msg = node.recv("temperature") # Wrong name — not in subs
# GOOD: declared topic matches recv() call
node = horus.Node(subs=["temperature"], tick=tick)
msg = node.recv("temperature")
TypeError in Tick Function
Symptom:
TypeError: tick() takes 0 positional arguments but 1 was given
Cause: Your tick function does not accept the node parameter. The scheduler passes the node instance automatically.
Fix:
# WRONG
def tick():
print("hello")
# CORRECT — tick must accept one argument (the node)
def tick(node):
print("hello")
This also applies to init and shutdown callbacks:
def my_init(node):
node.log_info("Starting up")
def my_shutdown(node):
node.log_info("Shutting down")
Multi-Process Issues
Topics Not Visible Across Processes
Symptom: Two Python scripts both use HORUS topics, but they cannot see each other's messages.
Cause: You are running python src/main.py directly instead of horus run. When you run Python directly, HORUS cannot set up the shared memory namespace, so each process gets its own isolated SHM region.
Fix: Always use horus run:
# WRONG — no SHM namespace, topics are isolated
python src/main.py
# CORRECT — horus sets up the namespace
horus run
If you need multiple processes that share topics, launch them all through HORUS:
# Terminal 1
cd my-project && horus run
# Terminal 2 (same project directory — shares the SHM namespace)
cd my-project && horus run src/second_process.py
Verifying topic visibility:
# While your application is running, check for active topics
horus topic list
# If topics appear here, they are in the correct namespace
# If empty, the publisher is not using `horus run`
Different SHM Namespaces
Symptom: Two HORUS projects cannot communicate via topics.
Cause: Each HORUS project gets its own SHM namespace (derived from the project directory). Topics from project A are invisible to project B by design.
Fix: If you need cross-project communication, run both node sets from the same project directory. Alternatively, structure your system as a single HORUS project with multiple entry points.
Virtual Environment Issues
horus Installed System-Wide, Python in a Venv
Symptom: horus run works, but import horus fails inside your virtual environment.
Cause: The horus-robotics package was installed into the system Python, but your venv does not inherit system packages.
Fix:
# Option 1: Install into the venv
source venv/bin/activate
pip install horus-robotics
# Option 2: If building from source
source venv/bin/activate
cd horus_py
maturin develop --release
Verify the fix:
source venv/bin/activate
python3 -c "import horus; print('OK')"
horus Installed in Venv, CLI Calls System Python
Symptom: python3 -c "import horus" works inside your venv, but horus run uses the wrong Python and fails with ModuleNotFoundError.
Cause: The horus CLI resolves Python from PATH. If the venv is not activated when you run horus run, it picks up the system Python which does not have the package.
Fix:
# Always activate the venv before running
source venv/bin/activate
horus run
Check which Python horus is using:
# Inside your tick function, add temporarily:
import sys
print(sys.executable)
If this prints a path outside your venv (e.g., /usr/bin/python3), activate the venv and try again.
Performance Issues
tick() Taking Too Long (GIL Blocking)
Symptom: Your node has a deadline or budget set, and you see warnings like:
[WARN] [MyNode] tick exceeded budget: 52ms (budget: 33ms)
Or horus monitor shows tick durations consistently over budget.
Cause: Python's Global Interpreter Lock (GIL) means only one thread runs Python code at a time. If your tick() does heavy computation in pure Python, it blocks the scheduler.
Fix:
Option 1: Use compute=True for C-extension work (NumPy, PyTorch, OpenCV):
import numpy as np
import horus
def heavy_tick(node):
data = node.recv("sensor")
if data is not None:
# NumPy releases the GIL internally
result = np.fft.fft(np.array(data))
node.send("processed", result.tolist())
node = horus.Node(
subs=["sensor"], pubs=["processed"],
tick=heavy_tick, rate=30,
compute=True # Runs on thread pool, GIL released during C calls
)
Option 2: Reduce work per tick:
def tick(node):
# BAD: Processing entire dataset every tick
for item in huge_list:
process(item)
# GOOD: Process in chunks
chunk = huge_list[node.tick_count % len(chunks)]
process(chunk)
Option 3: Lower the rate for heavy nodes:
# ML inference at 10 Hz, not 100 Hz
node = horus.Node(tick=run_model, rate=10, budget=0.08) # 80ms budget
High dropped_count / Messages Silently Lost
Symptom: horus monitor shows a nonzero dropped count on a topic, or you know messages are being sent but the subscriber never sees them.
Cause: For generic (dict-based) messages, HORUS serializes data with MessagePack. If the serialized size exceeds the slot size (default 8KB), the message is silently dropped.
Fix:
Check message size:
import msgpack
data = {"image": [0] * 10000}
size = len(msgpack.packb(data))
print(f"Serialized size: {size} bytes")
# If > 8192, the message will be dropped
Keep messages small:
# BAD: Sending large data through generic topics
node.send("image", {"pixels": huge_list})
# GOOD: Send metadata, store large data elsewhere
node.send("image.meta", {"width": 640, "height": 480, "frame_id": 42})
# BETTER: Use typed messages for fixed-size data (zero-copy, no size limit)
from horus import Image
node.send("camera", Image(...))
Use typed messages for large fixed-size data:
from horus import CmdVel, LaserScan
# Typed messages use zero-copy Pod transfer (~2.7us)
# No serialization, no size limit concerns
node = horus.Node(pubs=[CmdVel], tick=tick)
Monitor drops in real time:
horus monitor
# Check the "Drops" column in the Topics tab
Slow Dict Topics vs Typed Topics
Symptom: IPC latency is around 10us when sending dicts, but you expected the sub-microsecond latency advertised by HORUS.
Cause: String-named topics use MessagePack serialization (~10us round-trip). Typed topics use zero-copy Pod transfer (~2.7us round-trip). The sub-microsecond figures apply to Rust Pod messages.
Fix: Use typed messages for performance-critical paths:
from horus import CmdVel, LaserScan, Imu
# SLOW (~10μs) — dict serialized with MessagePack
node = horus.Node(pubs=["cmd_vel"], tick=lambda n: n.send("cmd_vel", {"linear": 1.0}))
# FAST (~2.7μs) — typed Pod, zero-copy
node = horus.Node(pubs=[CmdVel], tick=lambda n: n.send("cmd_vel", CmdVel(1.0, 0.0)))
For nodes where latency does not matter (loggers, displays, config), dict topics are perfectly fine.
Debugging Tools
horus topic Commands
Use these to inspect live topic data from the command line:
# List all active topics with publisher/subscriber counts
horus topic list
# Print messages as they arrive on a topic
horus topic echo sensor_data
# Measure the publish rate of a topic
horus topic hz sensor_data
These work regardless of whether the publisher is Python or Rust.
horus monitor
The real-time TUI monitor is the single best debugging tool:
# Terminal 1: Run your application
horus run
# Terminal 2: Open the monitor
horus monitor
What to check:
| Tab | Look For | Meaning |
|---|---|---|
| Nodes | State = "Running" for all nodes | If "Error" or missing, the node crashed |
| Nodes | Tick duration | If consistently over budget, tick is too slow |
| Topics | Publisher count = 0 | Nobody is sending to this topic |
| Topics | Subscriber count = 0 | Nobody is listening to this topic |
| Topics | Drops > 0 | Messages are being lost (see Messages Dropped) |
| Metrics | IPC latency | Should be <10us for dict topics, <3us for typed |
| Graph | Disconnected nodes | Topic name mismatch between publisher and subscriber |
Debug workflow:
1. Check Nodes tab
-> All nodes Running? If not, check terminal for exceptions
2. Check Topics tab
-> Topic exists? If not, topic name typo or publisher not started
-> Publishers > 0? If not, publisher node is not working
-> Subscribers > 0? If not, subscriber has wrong topic name
3. Check Metrics tab
-> Messages sent > 0? If not, publisher tick is not calling send()
-> Messages received > 0? If not, subscriber tick is not calling recv()
4. Check Graph tab
-> All nodes connected? If not, topic name mismatch
Checking Node Health
Use logging inside your nodes to trace execution:
def tick(node):
node.log_debug("tick started")
msg = node.recv("input")
if msg is not None:
node.log_info(f"Processing message: {msg}")
result = process(msg)
node.send("output", result)
node.log_debug("tick completed with message")
else:
node.log_debug("tick completed, no message")
If you see "tick started" in the logs but never "tick completed", the hang is inside your processing code.
Common Error Messages
RuntimeError: Topic 'X' not in publishers list
Cause: You called node.send("X", data) but "X" was not declared in the node's pubs parameter.
Fix:
# WRONG
node = horus.Node(pubs=["output"], tick=tick)
# then in tick: node.send("result", data) # "result" not in pubs!
# CORRECT
node = horus.Node(pubs=["result"], tick=tick)
# then in tick: node.send("result", data)
RuntimeError: Topic 'X' not in subscribers list
Cause: You called node.recv("X") but "X" was not declared in the node's subs parameter.
Fix: Add the topic to subs:
node = horus.Node(subs=["sensor_data"], tick=tick)
# now node.recv("sensor_data") works
RuntimeWarning: Logging outside scheduler context
Cause: You called node.log_info() (or another logging method) outside of init(), tick(), or shutdown(). Logging only works during scheduler callbacks.
Fix:
# WRONG — logging before scheduler starts
node = horus.Node(tick=tick)
node.log_info("Ready") # RuntimeWarning — dropped
# CORRECT — log inside callbacks
def init(node):
node.log_info("Ready") # Works
def tick(node):
node.log_info("Ticking") # Works
RuntimeError: Scheduler is already running
Cause: You called horus.run() twice, or called it while a scheduler is already active in the same process.
Fix: Only call horus.run() once. If you need to restart, let the first call finish (Ctrl+C or duration timeout), then call again.
OSError: [Errno 28] No space left on device
Cause: The /dev/shm tmpfs filesystem is full. HORUS stores shared memory topics here, and stale regions from crashed processes can accumulate.
Fix:
# Clean HORUS shared memory regions
horus clean --shm
# Check available space
df -h /dev/shm
# If still full, check what's using space
horus doctor
Patterns and Anti-Patterns
DO: Check recv() for None
def tick(node):
msg = node.recv("input")
if msg is not None:
process(msg)
# No message? That's OK — continue
DON'T: Assume recv() Always Returns Data
def tick(node):
msg = node.recv("input")
print(msg["value"]) # AttributeError if msg is None!
DO: Keep tick() Fast
def tick(node):
data = node.recv("sensor")
if data is not None:
node.send("output", quick_transform(data))
DON'T: Block in tick()
def tick(node):
import time
time.sleep(1) # Blocks the entire scheduler!
import requests
resp = requests.get("https://api.example.com") # Network I/O blocks!
If you need async I/O, use async tick functions:
async def tick(node):
# Async ticks are auto-detected and run on the async executor
data = await fetch_data()
node.send("output", data)
DO: Declare All Topics
node = horus.Node(
pubs=["status", "cmd_vel"],
subs=["sensor", "emergency_stop"],
tick=tick
)
DON'T: Use Undeclared Topics
node = horus.Node(pubs=["status"], tick=tick)
# In tick:
node.send("cmd_vel", data) # RuntimeError — not in pubs!
DO: Use horus run
horus run
DON'T: Run Python Directly
python src/main.py # Topics won't connect across processes
Getting Help
If you are still stuck:
-
Run diagnostics:
horus doctor horus clean --shm -
Add debug logging:
def tick(node): node.log_debug(f"State: {my_state}") -
Test with a minimal example: Strip your code down to the simplest possible case. Add complexity back one piece at a time until the error appears.
-
Check the monitor:
horus monitor -
Report the issue:
- GitHub: https://github.com/softmata/horus/issues
- Include: full error message + traceback, minimal code example, output of
horus --versionandpython3 -c "import horus; print(horus.__version__)", OS and Python version
Next Steps
- Quick Start (Python) — Build your first application
- Python API Reference — Full API documentation
- Choosing a Language — When to use Python vs Rust
- Performance — Optimization tips
- Common Mistakes — Beginner pitfalls
See Also
- Troubleshooting (Rust) — Rust-specific issues
- Debugging — Runtime diagnostics
- Monitor — Live system observation
- CLI Reference —
horus doctor,horus topic, andhorus clean