Skip to content

Pin an automation to one computer of a device group - #240

Merged
danielehrhardt merged 2 commits into
mainfrom
feat/routine-run-on
Oct 24, 2026
Merged

danielehrhardt merged 2 commits into
mainfrom
feat/routine-run-on

Conversation

@danielehrhardt

Copy link
Copy Markdown
Contributor

Problem

In a device group, a secondary that stands in for an offline primary fires every schedule itself. Automations that depend on files, folders or browser profiles that only exist on one computer then run on the wrong one and fail or do nothing (missing project folders, MODULE_NOT_FOUND, a browser profile that isn't there).

Change

A per-automation Run on setting: routines.run_on (migration 103) holds "primary", a device id (the same GroupDevice.id the Devices list uses) or NULL = any computer (today's behaviour).

  • Scheduler: every desktop computer runs the scheduler, but only fires what belongs to it (runsRoutineHere in remote/group.ts). Pinned automations fire only on their computer, also while another one stands in; unpinned ones fire where the group's schedules run (primary, or a secondary standing in). The stand-in catch-up (runMissedSince) skips pinned automations. The pinned computer catches up a slot it missed while away through the existing catchUpMissed / late-tick path, so the existing dedupe (caughtUp, last_run_at, lastScheduledStart) still guarantees one run per slot. On the primary, a pin to a computer that left the group falls back to the primary.
  • No stray writes: a computer only writes next_run_at and repairs last_status for automations it fires, so a secondary doesn't overwrite the primary's view of automations it doesn't run (routines replicate as whole rows).
  • Chats: a pinned automation only reuses a chat that lives on its own computer. Otherwise sendMessage would forward the run to the computer the chat lives on.
  • Run now (POST /api/routines/:id/run, routine_run): when the automation is pinned elsewhere, the request goes to that computer over a new link-only route, POST /api/link/routines/:id/run (group peers only, refused for the cloud). A secondary sends it to its primary, which passes it on. If that computer can't be reached, the request fails with a 409 device_offline that names the computer.
  • API / tools: runOn on Routine / RoutineInput, in the REST schema, and in routine_create / routine_update. The value can be "primary", a device id, a computer's name, or the primary's runner id as seen from a secondary; it is normalised to the stored form. A routine_update that only changes runOn doesn't make the automation fill-only (like pausing). Saving an unchanged pin to a computer that left the group still works.
  • Replication: the column goes through the existing replica triggers; a test checks that it arrives on the other computer.
  • Desktop: the automation editor shows a "Run on" select (Any device / each computer of the group, marked primary / this computer) for schedule and condition automations when the computer is in a device group, with a one-line hint about stand-in behaviour.

Notes

  • Routines replicate row by row, and the newer copy wins. A pinned secondary now writes run state (last_run_at, last_status) while the primary is up. If the two computers can't reach each other, a run on the secondary can win over an edit made on the primary at the same time. A secondary standing in has the same problem today; per-column merge would be a separate change.
  • Event automations (app, webhook) still run where the event arrives; the pin applies to schedules and condition checks.

Tests

  • scheduler.test.ts: 10 new tests. They cover: normalising names and ids into the stored pin; a stand-in that never fires or catches up an automation pinned to the primary (and leaves its next_run_at alone); a secondary that fires its own pins while not standing in; the primary skipping automations pinned to another computer unless that computer left the group; saving a stale pin; no chat reuse across computers; Run now refused with a clear message while offline; the pinned primary catching up a missed slot exactly once; a secondary catching up only its own pins after a sleep.
  • replica.test.ts: the pin replicates.
  • pnpm -r typecheck is clean. These suites pass: scheduler, routines, replica, mcp, devices-e2e, stand-in-waiting, automations, cloud-scope, remote-link, backup, remote-snapshot.

A per-automation Run on setting (routines.run_on: "primary" or a device id, null = any).
Every desktop computer runs the scheduler but fires only what belongs to it: pinned
automations only on their computer, also while another one stands in; the rest where the
group's schedules run. Run now on a pinned automation goes to its computer over the link,
or is refused clearly while that computer is offline. Exposed in the REST API, the
routine_create/routine_update tools and the automation editor.
…ill save

A pinned automation only goes on in a chat that lives on its computer. Run now passed
on between computers uses a link-only route, so app requests can't claim the pinned
hop. An unchanged pin to a computer that left the group no longer blocks saving.
@danielehrhardt
danielehrhardt merged commit 6bdf269 into main Oct 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant