apps/runner) is a standalone process that is separate from the main server. It polls the server for pending review tasks, executes them inside a Docker container, and reports results back. Nothing executes inside the main server process itself.
Why runners are separate
Isolation
Each review runs inside a Docker container. A crashed or misbehaving review cannot affect the main API service.
Horizontal scaling
Multiple runner instances can register against the same server, distributing review load across machines or containers.
Resource control
CPU, memory, and concurrency limits are configured on the runner side, not on the API server.
Lean server
The main server only queues tasks and processes results — it never executes user code or clones repositories.
Registration
Before a runner can accept tasks, it must register with the server using a Better Auth API key:RUNNER_TOKEN must correspond to a valid API key created through Better Auth.
Heartbeat mechanism
After registration, the runner sends periodic heartbeats to the server to signal that it is healthy and available. The server uses these heartbeats to track runner status.- Runner side: heartbeat interval is controlled by
RUNNER_HEARTBEAT_INTERVAL_MS - Server side: if no heartbeat is received within
RUNNER_HEARTBEAT_TIMEOUT_MS, the runner is marked as offline
An offline runner does not block the queue — tasks remain pending and are picked up as soon as a healthy runner comes back online.
Task lifecycle
Each task moves through these states:RUNNER_POLL_INTERVAL_MS. When a task is found, task-executor.ts claims it and hands it off to the Docker executor.
Docker executor
All reviews execute inside a Docker container. The image is configured withDOCKER_EXECUTOR_IMAGE:
apps/runner/Dockerfile.executor.
Environment variables
Required
| Variable | Description |
|---|---|
RUNNER_SERVER_URL | URL of the main API server |
RUNNER_TOKEN | Better Auth API key for runner authentication |
RUNNER_NAME | Display name for this runner instance |
DOCKER_EXECUTOR_IMAGE | Docker image used to execute reviews |
Optional
| Variable | Description |
|---|---|
RUNNER_MAX_CONCURRENT_JOBS | Maximum reviews to run in parallel |
RUNNER_POLL_INTERVAL_MS | How often to poll for new tasks |
RUNNER_POLL_TIMEOUT_MS | Timeout for each poll request |
RUNNER_HEARTBEAT_INTERVAL_MS | How often to send a heartbeat |
RUNNER_REQUEST_TIMEOUT_MS | Timeout for HTTP requests to the server |
RUNNER_CLONE_DEPTH | Git clone depth used during review |
DOCKER_MEMORY_LIMIT | Memory limit for executor containers |
DOCKER_CPU_LIMIT | CPU limit for executor containers |
DOCKER_NETWORK_MODE | Docker network mode for containers |
DOCKER_WORKSPACE_BASE | Base path for container workspaces |
DOCKER_TIMEOUT_SECONDS | Timeout for a single container execution |
DOCKER_POOL_SIZE | Pre-warmed container pool size |
DOCKER_HOST | Docker daemon socket or host |
RUNNER_HEARTBEAT_TIMEOUT_MS to decide when a runner is considered offline.
Deployment
The recommended way to deploy a runner is withdocker-compose.runner.yml from the repository root:
Monitoring
Two signals indicate runner health:-
Heartbeat status — visible in the Runner management page of the dashboard. A runner that has stopped sending heartbeats will be shown as offline after
RUNNER_HEARTBEAT_TIMEOUT_MSelapses. -
Task queue depth — visible via the queue monitoring interface (available in development at
/api/queuedash). A growing queue of pending tasks with no active runners indicates a registration or connectivity problem.