buildd
Getting Started

Runner Setup

Run tasks locally with a standalone worker that connects to buildd.dev

Runner Setup

The Runner is a standalone worker that connects to buildd.dev. It runs on your machine (or a remote workspace like Coder) and executes tasks using the Claude Agent SDK.

Prerequisites

  • Bun runtime
  • A buildd.dev account with an API key

Quick Start

cd apps/runner
bun install
BUILDD_API_KEY=bld_xxx PROJECTS_ROOT=~/projects bun start

The runner is headless by default — there is no web UI to open. It starts an HTTP server only when you pass --debug or set PORT. Without one of those, nothing is listening on port 8766 and http://localhost:8766 will not connect.

Configure it in one of three ways:

  • BUILDD_API_KEY env var — the simplest path, and it takes priority over the config file.
  • buildd login — writes apiKey (and builddServer) into ~/.buildd/config.json.
  • bun start --debug — starts the debug HTTP server on port 8766, which exposes an OAuth login route (/auth/login) and a POST /api/config endpoint. Only in this mode does opening http://localhost:8766 do anything.

With no API key and no serverless flag, a headless runner prints No API key — run with --debug or set BUILDD_API_KEY and does no work.

PROJECTS_ROOT is effectively mandatory: the runner exits at startup if it cannot resolve at least one project root.

Configuration

All config is stored in ~/.buildd/config.json. The file path can be overridden with BUILDD_CONFIG, and the containing directory with BUILDD_HOME.

Config Fields

FieldTypeDefaultDescription
apiKeystring—Buildd API key (bld_xxx)
serverlessbooleanfalseRun without an API key; forced to false whenever a key resolves
modelstringclaude-sonnet-4-6Model to use (the standard tier default)
maxConcurrentnumber3Max concurrent workers
acceptRemoteTasksbooleantrueAccept task assignments from the dashboard
bypassPermissionsbooleanfalseSkip permission prompts (dangerous commands still blocked)
openBrowserboolean—Auto-open browser on startup
builddServerstringhttps://buildd.devServer URL override
localUiUrlstringsee URL ResolutionDirect-access URL for this instance, used only in --debug mode
pusherKeystring—Pusher key for realtime events
pusherClusterstring—Pusher cluster (e.g. us2)
pusherChannelPrefixstring''Channel prefix for environment isolation
llmProviderstringanthropicLLM provider: anthropic or openrouter
llmApiKeystring—Provider API key (for OpenRouter, etc.)
llmBaseUrlstring—Custom LLM base URL
maxTurnsnumber—Max turns per worker session; unset means no limit

Unrecognised keys in config.json are ignored at load time.

Environment Variables

VariableConfig fieldWhich wins
BUILDD_API_KEYapiKeyenv
BUILDD_SERVERbuilddServerenv
MODELmodelenv
LLM_PROVIDERllmProviderenv
LLM_API_KEYllmApiKeyenv
LLM_BASE_URLllmBaseUrlenv
LOCAL_UI_URLlocalUiUrlenv (but both lose to headless mode)
PUSHER_KEY / NEXT_PUBLIC_PUSHER_KEYpusherKeyenv
PUSHER_CLUSTER / NEXT_PUBLIC_PUSHER_CLUSTERpusherClusterenv
PUSHER_CHANNEL_PREFIXpusherChannelPrefixenv
MAX_CONCURRENTmaxConcurrentconfig.json — see below

MAX_CONCURRENT is the one exception to "env overrides config". The runner resolves it as config.maxConcurrent || MAX_CONCURRENT || 3, so any maxConcurrent value in config.json silently wins and MAX_CONCURRENT is ignored. To raise concurrency on a runner that has a config file, edit maxConcurrent — or remove it from the file first.

Env vars with no config-file equivalent:

VariableEffect
PROJECTS_ROOTComma-separated project directories (e.g. ~/projects,~/work). The runner exits if none resolve
PORTListen port (default 8766) — setting it at all also enables the HTTP server
BUILDD_CONFIGConfig file path (default ~/.buildd/config.json)
BUILDD_HOMEConfig directory (default ~/.buildd)
BUILDD_RUNNER_POLL_MINTask-poll cadence in minutes (default 60)
BUILDD_WORKSPACE_ISOLATION_ROOTOpt-in structural workspace isolation root

Note that every one of these is resolved with ||, not ??. A falsy value in config.json — maxConcurrent: 0, builddServer: "" — falls through to the next source rather than being honoured.

CLI Flags

FlagEffect
--debugStart the HTTP server and debug UI on PORT (default 8766)
--doctorRun diagnostics and exit non-zero on any error
--fixWith --doctor, apply available auto-fixes
--env-verify (or env verify)Verify the environment
--jsonWith --env-verify, emit JSON

There is no argument parser — any other flag is silently ignored.

URL Resolution

This whole ladder applies only in --debug mode. A headless runner reports the sentinel headless://<hostname> as its identity and ignores both LOCAL_UI_URL and localUiUrl entirely, so there is nothing for the dashboard to link to.

localUiUrl determines how the dashboard links back to a debug-mode instance (the "Open" and "Open Terminal" buttons). It is resolved once at startup:

  1. Headless (no --debug, no PORT) → headless://<hostname>, and the steps below are never reached
  2. LOCAL_UI_URL env var
  3. localUiUrl in ~/.buildd/config.json
  4. Tailscale auto-detect — runs tailscale ip -4 and uses the IPv4 address
  5. http://localhost:<PORT> (fallback)

Remote Workspaces (Coder, SSH, etc.)

To get working dashboard links from a remote machine, run in debug mode and set the URL explicitly:

// ~/.buildd/config.json on the remote machine
{
  "localUiUrl": "http://<tailscale-ip>:8766"
}

Or via env var:

LOCAL_UI_URL=http://<tailscale-ip>:8766 bun start --debug

If the Tailscale CLI is installed on the machine, the IP is auto-detected and you don't need to set anything — again, only in debug mode.

How It Works

The runner keeps two timers, not one:

TimerIntervalPurpose
Liveness ping60 secondsPure "runner is alive" signal; drives the online indicator
Task-poll cycleBUILDD_RUNNER_POLL_MIN minutes, default 60Reconcile local workers, claim-fallback, knowledge ingest

Both are skipped entirely when serverless is set. Extra heartbeats are also sent at startup, after a claim, and on demand over Pusher.

Server-side, a runner shows as online while its last beat is within 1.5× the task-poll interval, stale between 1.5× and 2.5×, and is dropped from runner queries beyond 2.5×.

  1. The runner starts and begins both timers
  2. The dashboard shows connected instances under "Connected Agents"
  3. When a task is created and assigned, the runner claims it and spawns a Claude Agent SDK session
  4. Progress is reported back to buildd.dev in real-time via API + Pusher
  5. In --debug mode, the dashboard can link directly to the runner for live terminal output

On this page