run <file>.run.ts runs a script. What happens next depends on what the script did while its top level ran.
One-shot or long-runner
- One-shot: the script never called
.on(...)orevery(...). It runs like a command. Output streams to your terminal, it returns with the script’s own exit code, and it leaves nothing behind whether it succeeded or failed. Nothing is registered and nothing retries. - Long-runner: the script registered a trigger or a schedule. Its top-level output streams to your terminal until it settles, then it is moved to the background as a job and your shell returns:
Identity: one file, one job
A job is identified by its file’s absolute path, and its 5-character id is derived from it. Running the same file again replaces the running job with the new version, under the same id. That is the deploy flow: edit,run the file, done. Subscriptions and schedules don’t churn on a replace.
background or retrying state is started again when the machine boots. A job’s log is one file per job, rewritten each time the job (re)starts, and run logs -f notices and re-prints from the top.
What a job runs with
A job is started by the machine, and it inherits more than a bare environment:
Jobs currently run as
root, so files they create are owned by root even under /home/boxd. Use sudo chown boxd:boxd if you need to edit them from a shell afterwards.
States
Waiting on a connection
Before starting a script, boxd works out which connections it needs: every integration it imports, at the scope it uses, across all local imports. Anything missing is reported up front, and the job waits instead of failing at its first call:run auth linear) and the job starts by itself. The message always names the scope. A personal Linear does not satisfy linear.scoped("ops"). The console shows the same job as Waiting to connect.
Crashes and retries
A long-running job that exits with an error is restarted with exponential backoff (2s, 4s, 8s, 16s) up to five attempts, then parked asfailed with the log kept for inspection. A one-shot is never retried and just reports its exit code. If the job crashes while you’re still watching the first run, your terminal comes back at the first retry rather than waiting out the ladder:
failed job is not restarted on boot or after a fork. That’s deliberate, so a crash loop can’t hide by resetting itself. Fix the script and re-run the file, or run restart <id>. Errors thrown inside a trigger or schedule handler are logged and don’t count as crashes.
Long-runners should be restart-tolerant. boxd restarts them on fork, snapshot restore, ownership change, and whenever the team’s set of integrations changes. Nothing in flight is preserved across a restart, so put anything that must survive in object(...).
Sleep and wake
A machine with automations still auto-suspends and hibernates like any other. An idle job makes no network calls of its own, so it never keeps the machine awake.- Schedules: the platform holds the next time any
every(...)on the machine is due and wakes a hibernated machine shortly before it. A cron slot that a late wake slipped past fires once on wake. - Events: a trigger firing wakes the machine and delivers the event. Events that arrive while the machine is waking are held for up to 15 minutes. Anything older than that is dropped rather than delivered arbitrarily late.
Forks and snapshots
A forked machine, or one restored from a snapshot, inherits every job of its source and starts them immediately (exceptfailed ones). The console write-up and diagram come along, since they’re keyed by the script’s content. Forking is the way to run the same automation many times over.
The console
Automations in the sidebar lists every long-running job across your machines. The page is read-only. The files on the machine are the source of truth. Each card shows:- a generated name (editable, click the pencil on the detail view) and one-line summary.
- Triggers in plain English (Every morning at 9am, Linear issue created) and Uses, the integrations it calls, with their logos.
- a status pill: On (or Off), Waiting to connect, Retrying (2/5), Failed, Stopped.
- the last run time. A waiting card says exactly what it’s waiting on, and a failed card shows the last error and the
run logs <id>command to inspect it.