Overview
The runner acts as a daemon that:Remote Control
Enables session management from web and Telegram Mini App
Session Isolation
Spawns detached sessions that survive runner restarts
Auto-Updates
Automatically restarts when CLI is upgraded
Health Monitoring
Tracks session health and prunes stale processes
Runner Commands
Start Runner
Start the runner as a detached background process:- Register the machine with the hub
- Open a WebSocket connection for real-time communication
- Start an HTTP control server on a random port (localhost only)
- Begin heartbeat monitoring every 60 seconds
If a runner is already running with the same CLI version, the command exits immediately. If the version differs, the old runner is stopped first.
Stop Runner
Gracefully stop the background runner:- Updates runner state to “shutting-down” on the hub
- Closes WebSocket connection
- Stops HTTP control server
- Cleans up state files and lock files
- Exits the process
Runner Status
View detailed runner diagnostics:- Runner process ID and uptime
- CLI version and modification time
- HTTP control port
- Last heartbeat timestamp
- Path to runner log file
List Active Sessions
Show all sessions managed by the runner:Stop Specific Session
Terminate a single session by ID:View Runner Logs
Get the path to the latest runner log file:Runner State and Heartbeat
The runner maintains state in~/.hapi/runner.state.json (or $HAPI_HOME/runner.state.json):
Heartbeat System
Every 60 seconds (configurable viaHAPI_RUNNER_HEARTBEAT_INTERVAL), the runner:
Prune Stale Sessions
Checks each tracked PID with
isProcessAlive() and removes dead sessions from the tracking map.Version Check
Compares CLI binary modification time with startup mtime. If changed, spawns new runner and waits to be killed.
PID Ownership Verification
Verifies the runner still owns the state file. If another runner took over, terminates self.
The heartbeat uses a guard to prevent concurrent executions. If the previous heartbeat is still running, the current cycle is skipped.
Configuration
Configure runner behavior with environment variables:HAPI_RUNNER_HEARTBEAT_INTERVAL
Control how often the runner performs health checks:HAPI_RUNNER_HTTP_TIMEOUT
Timeout for HTTP control requests:hapi runner stophapi runner listhapi runner stop-session- Internal runner control operations
Runner Architecture
HTTP Control Server
The runner starts a local Fastify server on127.0.0.1 with a random port. This server provides:
POST /session-started
POST /session-started
Purpose: Sessions report themselves after creationRequest:Response:
POST /list
POST /list
Purpose: List all tracked sessionsResponse:
POST /stop-session
POST /stop-session
Purpose: Terminate a specific sessionRequest:Response:
POST /stop
POST /stop
Purpose: Gracefully shutdown the runnerResponse:
WebSocket Communication
The runner maintains a WebSocket connection to the hub on the/cli namespace:
Runner → Hub:
machine-alive: Heartbeat every 20 secondsmachine-update-metadata: Static machine info changesmachine-update-state: Runner status changes
spawn-happy-session: Spawn new session remotelystop-session: Stop session by IDstop-runner: Request shutdown
Use Cases
Remote Development
Control sessions on your development machine from your phone or a remote browser
Long-Running Tasks
Keep sessions alive while closing your terminal or laptop
Multi-Machine Setup
Manage sessions across multiple development machines from a single web interface
Team Collaboration
Team members can spawn sessions on shared development servers
Session Spawning
The runner supports two types of session spawning:Runner-Spawned Sessions (Remote)
Initiated via web/mobile app:Runner Spawns Process
Runner spawns detached HAPI process with
--hapi-starting-mode remote --started-by runnerTerminal-Spawned Sessions
User runshapi directly in terminal:
- CLI auto-starts runner if not running (optional behavior)
- HAPI process calls
notifyRunnerSessionStarted()webhook - Runner tracks session with
startedBy: 'hapi directly - likely by user from terminal' - Session continues independently
Automatic Version Detection
The runner automatically restarts when you upgrade the HAPI CLI:This ensures you’re always running the latest runner code without manual intervention.
Lock File and State Management
The runner uses file-based locking to prevent multiple instances: Lock file:~/.hapi/runner.state.json.lock
- Created with O_EXCL flag (atomic operation)
- Contains PID for debugging
- Automatically cleaned up on graceful shutdown
~/.hapi/runner.state.json
- Written without lock (concurrent reads are safe)
- Contains PID, port, version, and heartbeat data
- Used by CLI commands to communicate with runner
Troubleshooting
Runner Won’t Start
Lock file already held
Lock file already held
Another runner is already running. Check status with:If you believe this is an error, stop the existing runner:
Port conflict
Port conflict
The HTTP control server uses a random port. If all ports are exhausted (unlikely), check available ports:
Authentication failure
Authentication failure
Ensure
CLI_API_TOKEN is set correctly:Sessions Not Appearing
- Check if runner is running:
hapi runner status - Check runner logs:
tail -f $(hapi runner logs) - Verify session webhook reached runner (should see
[RUNNER RUN] Session reportedin logs) - Check if session process is alive:
hapi runner list
Runner Keeps Restarting
This usually indicates the CLI binary is being modified repeatedly. Common causes:- Active development with
bun run build:cli:exerunning in watch mode - File system timestamp issues
- Antivirus software modifying the binary
Advanced: Directory Creation Approval
When spawning sessions remotely, the runner can request approval for directory creation: Error handling includes specific messages for:EACCES: Permission deniedENOTDIR: File exists at pathENOSPC: Disk fullEROFS: Read-only filesystem
Related Topics
Worktrees
Git worktree support for isolated branches
Configuration
All environment variables and settings
Namespaces
Multi-user isolation on shared hubs