CloudXR Runtime#
The CloudXR runtime is what actually stream sensor data and visual data between various I/O devices (e.g. XR headset, gloves, etc) and Isaac Teleop. They can be started two ways, both on top of the same code path:
CLI —
python -m isaacteleop.cloudxrfrom a dedicated terminal.Python API —
CloudXRLauncher, for embedding the runtime inside an existing Python application (for example an Isaac Sim or Isaac Lab script) so no second terminal is required.
This page is the reference for both. For a first-time walkthrough with screenshots, start at Quick Start; come back here when you need the programmatic API, a full list of environment variables, or troubleshooting detail.
Two ways to run#
Command line#
python -m isaacteleop.cloudxr [options]
Flags:
Flag |
Description |
|---|---|
|
CloudXR install directory. Default: |
|
Optional |
|
Accept the NVIDIA CloudXR EULA non-interactively (CI, containers, unattended scripts). |
|
Enable the OOB teleop control hub and USB-adb headset automation. See Out-of-Band Teleop Control. |
Python API#
from isaacteleop.cloudxr import CloudXRLauncher
with CloudXRLauncher(accept_eula=True) as launcher:
# runtime + WSS proxy are running
...
The CLI above is a thin wrapper around this class; every flag maps directly to a constructor argument.
CloudXRLauncher#
class CloudXRLauncher:
def __init__(
self,
install_dir: str = "~/.cloudxr",
env_config: str | pathlib.Path | None = None,
accept_eula: bool = False,
setup_oob: bool = False,
) -> None: ...
The launcher starts work immediately in __init__: it resolves env
configuration, verifies the EULA, cleans up any stale sentinel files,
spawns the runtime subprocess, waits up to 30 seconds for readiness,
and then starts the WSS proxy thread. Construction therefore returns
either with a fully running runtime or with a RuntimeError.
Constructor arguments#
Argument |
Description |
|---|---|
|
CloudXR install directory. Used to resolve the run directory
( |
|
Optional path to a |
|
If |
|
Enable the OOB teleop control hub in the WSS proxy. See Out-of-Band Teleop Control for details. |
Methods and properties#
Member |
Description |
|---|---|
|
Signal the WSS proxy to shut down and terminate the runtime
process group (SIGTERM, then SIGKILL after 10 s). Safe to call
multiple times, including when nothing is running. Raises
|
|
Raise |
|
|
|
Context-manager protocol. |
At-exit cleanup#
The launcher registers stop() with atexit on first
successful construction, so the runtime is stopped even if the
embedding process exits abnormally (unhandled exception, SystemExit).
An explicit stop() call, or exiting a with block, still runs
cleanup immediately — the atexit hook is a safety net, not a
substitute.
Error semantics#
|
Meaning |
|---|---|
|
EULA refused, or runtime did not reach readiness within
|
|
The runtime process group did not exit after SIGTERM and
SIGKILL. The |
|
Runtime subprocess exited, or WSS proxy thread stopped. Call
|
Integration pattern#
Minimal#
The simplest embedding is a with block around the code that needs
the runtime:
from isaacteleop.cloudxr import CloudXRLauncher
with CloudXRLauncher(accept_eula=True) as launcher:
run_teleop_session() # your app's main work
On exit (normal or exception) the runtime subprocess and WSS proxy are torn down.
Realistic embedding#
A longer-running application typically wants to (a) detect a crashed
runtime during the session, (b) respond to SIGINT/SIGTERM cleanly, and
(c) guarantee teardown even if the signal handler never fires. The
pattern below mirrors what python -m isaacteleop.cloudxr does
internally:
import signal
import time
from isaacteleop.cloudxr import CloudXRLauncher
launcher = CloudXRLauncher(
install_dir="~/.cloudxr",
env_config=None,
accept_eula=True,
)
try:
stop = False
def _on_signal(sig, frame):
nonlocal stop
stop = True
signal.signal(signal.SIGINT, _on_signal)
signal.signal(signal.SIGTERM, _on_signal)
while not stop:
launcher.health_check() # raises if runtime or proxy died
do_one_tick_of_your_app()
time.sleep(0.1)
finally:
launcher.stop()
Notes:
health_check()is cheap (it polls aPopenand checks a thread flag); calling it every tick is fine.The
finallyclause is redundant with theatexithook under normal exits, but keeps teardown deterministic for embedders that rely on ordered shutdown.Construction is synchronous and blocks the calling thread for up to 30 s while waiting for the runtime to become ready. Do not call it from inside a running asyncio event loop — use
asyncio.to_thread()(or construct before the loop starts). The WSS proxy itself runs on its own thread with an independent event loop.
Files and logs#
Everything the runtime persists lives under install_dir (default
~/.cloudxr):
~/.cloudxr/
├── openxr_cloudxr.json # OpenXR runtime manifest (staged from the SDK)
├── libopenxr_cloudxr.so # OpenXR runtime library (staged from the SDK)
├── run/
│ ├── cloudxr.env # final KEY=value env, source this in other terminals
│ ├── eula_accepted # EULA acceptance marker
│ ├── runtime_started # sentinel — created once the runtime is ready
│ ├── ipc_cloudxr # UNIX socket used between Monado and CloudXR
│ ├── monado.pid # Monado PID (for stale-process cleanup)
│ └── cloudxr.pid # CloudXR native service PID
└── logs/
├── runtime_stderr.log # Python + Vulkan/GPU init diagnostics
├── cxr_server.<ts>.log # native CloudXR server log (one per run)
└── wss.<ts>.log # WSS proxy log (one per run)
Sourcing the env file#
To run another process (e.g. Isaac Sim) against this runtime from a different terminal, source the env file:
source ~/.cloudxr/run/cloudxr.env
This sets XR_RUNTIME_JSON, NV_CXR_RUNTIME_DIR,
NV_CXR_OUTPUT_DIR, and the user-configurable CloudXR variables so
an OpenXR client finds the CloudXR runtime.
Stale-runtime cleanup#
If the previous runtime crashed without cleaning up, the sentinel files may still be present. On startup the launcher:
Looks for
run/ipc_cloudxr. If present, usesfuser -k -TERMto ask any process still holding the socket to exit.Removes
run/ipc_cloudxr,run/runtime_started,run/monado.pid, andrun/cloudxr.pid.Starts the new runtime in a fresh process group.
If construction still fails with “runtime failed to start within 30 s”,
check logs/runtime_stderr.log and the newest logs/cxr_server.*.log.
Environment variables#
Runtime#
These control the CloudXR runtime itself. Defaults come from
EnvConfig; override via --cloudxr-env-config (CLI) or
env_config= (API).
Variable |
Default |
Description |
|---|---|---|
|
|
Redirect runtime stdout/stderr to files under |
|
|
Enable OpenXR push-device extensions used by Isaac Teleop. |
|
|
Enable tensor-data streaming channel. |
|
|
CloudXR device profile. See the CloudXR SDK docs for valid values. |
|
(from ``install_dir``) |
Resolved automatically; subprocesses inherit it to locate the run/log directories. |
The following are resolved by the launcher from install_dir and
cannot be overridden from the env file (they are silently dropped
with a warning if you try):
XR_RUNTIME_JSON— path to the stagedopenxr_cloudxr.json.XRT_NO_STDIN— alwaystrue; disables Monado stdin.NV_CXR_RUNTIME_DIR— therun/directory underinstall_dir.NV_CXR_OUTPUT_DIR— thelogs/directory underinstall_dir.
LD_LIBRARY_PATH#
The launcher prepends the bundled SDK directory to LD_LIBRARY_PATH
before spawning the subprocess so that libcloudxr.so and
libopenxr_cloudxr.so are found. The runtime also loads
libcloudxr.so with RTLD_DEEPBIND to prevent symbol conflicts
with host applications that have already loaded an incompatible
OpenSSL.
WSS proxy and OOB#
The WSS proxy and OOB hub honor their own environment variables
(PROXY_PORT, CONTROL_TOKEN, TELEOP_STREAM_SERVER_IP, …).
Those are documented with the hub they configure; see
Out-of-Band Teleop Control.
EULA#
The first run prompts the user to accept the NVIDIA CloudXR EULA.
Acceptance is recorded at ~/.cloudxr/run/eula_accepted and
remembered across runs.
To bypass the prompt:
CLI:
python -m isaacteleop.cloudxr --accept-eula.API:
CloudXRLauncher(accept_eula=True).
For non-interactive environments (CI, containers, child processes
without a controlling TTY) accept_eula=True is required; the
interactive prompt will otherwise raise RuntimeError via
SystemExit on EOF.
See License for the full license text and licensing notes.
Troubleshooting#
Runtime did not start within 30 s#
CloudXRLauncher raises RuntimeError: CloudXR runtime failed
to start within 30s. The exception message already includes a tail
of logs/runtime_stderr.log and the newest
logs/cxr_server.*.log; those are the first files to inspect.
Common causes:
No compatible GPU / Vulkan loader available in the subprocess. Check
runtime_stderr.logfor Vulkan errors.LD_LIBRARY_PATHpulled in an incompatible OpenSSL (host apps like Isaac Sim sometimes do this). The launcher loadslibcloudxr.sowithRTLD_DEEPBINDto mitigate; confirm your environment hasn’t disabled that.The previous runtime is still alive holding
run/ipc_cloudxr. The launcher triesfuser -k -TERMautomatically; iffuseris not onPATH, installpsmiscor terminate the stale process manually.
EULA prompt hangs in a non-interactive context#
If no eula_accepted marker exists and stdin is not a TTY, the
launcher raises RuntimeError: CloudXR EULA was not accepted. Pass
accept_eula=True (or --accept-eula) for unattended runs.
Runtime dies mid-session#
health_check() will raise
RuntimeError: CloudXR runtime process exited unexpectedly. The
runtime’s exit code is not surfaced directly; read
logs/cxr_server.<ts>.log from the aborted run for the cause. Call
stop() to clean up the surviving WSS thread before constructing
a new launcher.
WSS proxy thread stopped unexpectedly#
health_check() reports RuntimeError: CloudXR WSS proxy thread
stopped unexpectedly. The thread logs its traceback via the
isaacteleop.cloudxr.launcher logger and writes request-level
details to logs/wss.<ts>.log. Enable Python logging at INFO or
DEBUG on that logger to capture the startup exception.
See also#
Quick Start — end-to-end first-run tutorial.
Out-of-Band Teleop Control — OOB control hub sharing the WSS TLS port.
License — EULA text and Isaac Teleop / CloudXR licensing.