Skip to main content
run <file>.run.ts runs a script. What happens next depends on what the script did while its top level ran.

One-shot or background job

  • One-shot. The script never called .on(...) or every(...). 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.
  • Background job. 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:
The detection is automatic. There is no flag.

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.
Jobs survive reboots. Everything in the 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 starts or restarts, 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:
Connect the named integration (the console’s Integrations page, or 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 background job that exits with an error is restarted with exponential backoff (2s, 4s, 8s, 16s) up to 5 attempts, then stopped as failed 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 instead of waiting out the ladder:
When the ladder is exhausted:
A 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. Background jobs should be restart-tolerant. boxd restarts them on an ownership change, whenever the organization’s set of integrations changes, and when you start one that a fork or a snapshot brought along paused. Nothing in flight is preserved across a restart, so put anything that must survive in object(...).

Sleep and wake

A machine with automations still hibernates when idle 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 sleeping machine, on standby or hibernated, shortly before it. A cron slot that a late wake slipped past fires once on wake.
  • Events. A trigger firing wakes a sleeping machine, on standby or hibernated, 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 instead of being delivered late.

Forks and snapshots

A forked machine, or one created from a snapshot, inherits the jobs of its source machine paused. The source machine is usually still running them, and so is every other machine created from the same snapshot, so starting them on the new machine would handle every schedule and every event twice: two digests posted, two replies to the same issue. A paused job doesn’t run and gets no events until you start it there. This covers every way of getting a copy:
  • A fork brings the jobs its source machine has running, retrying, waiting on a connection, or already paused.
  • A snapshot captures the same set when it’s saved, so it doesn’t matter whether the source machine still exists by the time you create a machine from it. Saving the snapshot again captures the set again, for that version.
  • A team’s default snapshot is a snapshot like any other: every new team machine created from it starts its jobs paused.
failed and exited jobs don’t come along, since nothing was going to restart them anyway. An isolated copy gets no automations at all. Each surface tells you when it happens:
boxd snapshots save says how many automations it captured, and boxd machine new --from-snapshot how many the new machine started with paused. With --json, both, and machine fork, carry the count as paused_automations. The SDKs set the same field on what fork(), create(from_snapshot=...) and snapshots.create() return, and warn once. The console says so in the fork and snapshot dialogs and in the confirmation afterwards, and shows the job with a Paused badge. On the new machine, run jobs lists them, and run restart <ID> starts one there. Re-running its file does the same. From then on it is that machine’s job like any other: it restarts on boot and gets its own events. The console write-up and diagram come along either way, since they’re keyed by the script’s content. To run the same automation many times over, fork or create the machines, then run restart the job on each of them.

The console

Automations in the sidebar lists every background 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 a 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.
A banner at the top counts automations that need attention. Filters: All, Active, Needs attention. Search matches the script’s name and file path. Open a card for the detail view: the machine and file, How it flows (a pan-and-zoom diagram of triggers, schedules, integrations and the machine, with labelled arrows and a description on hover over each node) and a write-up in four sections, Overview, Behaviour, Integrations and Notes. The same cards appear on each machine’s Automations tab, and its Overview shows compact tiles.

Generated documentation

The name, summary, trigger wording, diagram and write-up are generated automatically a few minutes after an automation first runs, from the script and its local imports. Before a script’s source leaves the machine, anything that looks like a credential (API-key prefixes, tokens, long random strings) is redacted, and the write-up treats those as opaque secrets. When you change the script, the documentation is regenerated with the previous version as reference, so names and diagram layout stay stable across edits. Very large scripts are summarized from a capped portion of the source and the write-up says so. Only background jobs are documented. A one-shot script leaves nothing behind.