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

ErrorJump ToQuick Fix
ModuleNotFoundError: No module named 'horus'Import Errorspip install horus-robotics
ModuleNotFoundError: No module named '_horus'Native Extension MissingRebuild with maturin develop --release
Failed to create TopicTopic Creation Errorshorus clean --shm
Permission denied on /dev/shmSHM Permission DeniedFix /dev/shm permissions
recv() always returns NoneTopic Not FoundCheck topic name match
Topics not visible across processesMulti-Process IssuesUse horus run, not python directly
High dropped_countMessages DroppedKeep messages under slot size
tick() taking too longGIL BlockingRelease GIL with compute=True
TypeError in tick functionWrong Tick SignatureUse def tick(node):
Version mismatch (CLI vs Python)Version MismatchReinstall both from same source
horus: command not foundCLI Not FoundAdd ~/.cargo/bin to PATH

Quick Diagnostic Steps

When your HORUS Python application is not working:

  1. Verify imports: python3 -c "import horus; print(horus.__version__)"
  2. Check the Monitor: Run horus monitor in a second terminal to see active nodes and topics
  3. Verify Topics: Ensure publisher and subscriber use the exact same topic names
  4. Check shared memory: Run horus clean --shm to remove stale regions from a previous crash
  5. Test individually: Run nodes one at a time to isolate the problem
  6. Check CLI health: Run horus doctor to 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 horus from a wheel built for a different Python version (e.g., built for 3.11, running 3.12)
  • The .so file 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:

TabLook ForMeaning
NodesState = "Running" for all nodesIf "Error" or missing, the node crashed
NodesTick durationIf consistently over budget, tick is too slow
TopicsPublisher count = 0Nobody is sending to this topic
TopicsSubscriber count = 0Nobody is listening to this topic
TopicsDrops > 0Messages are being lost (see Messages Dropped)
MetricsIPC latencyShould be <10us for dict topics, <3us for typed
GraphDisconnected nodesTopic 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:

  1. Run diagnostics:

    horus doctor
    horus clean --shm
    
  2. Add debug logging:

    def tick(node):
        node.log_debug(f"State: {my_state}")
    
  3. 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.

  4. Check the monitor:

    horus monitor
    
  5. Report the issue:

    • GitHub: https://github.com/softmata/horus/issues
    • Include: full error message + traceback, minimal code example, output of horus --version and python3 -c "import horus; print(horus.__version__)", OS and Python version

Next Steps


See Also