Skip to main content

Automation

Overview

Automation is the rule engine. It watches for things happening, checks whether they meet your conditions, and runs actions.

trigger → conditions (all must pass) → actions (run in order)

That is the whole model, and it is deliberately small. "When a download completes, and its label is movies, and its ratio is above 2.0 — move it, then stop seeding it."

It is a core module (id automation, permissions automation.view / automation.manage), and it is what Smart Download and RSS both hook into.

Why / when to use it

  • Post-processing. Move a completed download somewhere else; hand it to Media Manager for renaming.
  • Seeding hygiene. Stop or delete a torrent once it has hit your ratio target.
  • Integration. Fire a webhook into something UltraTorrent knows nothing about.
  • Reacting to your library. A show ends — convert its RSS rule to backfill, automatically.

If you find yourself doing the same thing by hand twice a week, it belongs here.

Prerequisites

  • A working engine (automation declares it as a hard dependency).
  • automation.view to see rules and their logs; automation.manage to create or change them.

Concepts

Trigger — the event that starts a rule. A rule has exactly one.

Condition — a { field, op, value } test against the event's context. Conditions are ANDed: every one must pass. Zero conditions means the rule always matches.

Action — what to do. A rule has an ordered list, and they run sequentially. The first action to throw aborts the remaining actions of that rule and logs the run as failed.

Priority — an integer. Higher priority runs first. Every matching rule runs; there is no stop-after-first-match.

Rising edge — a rule only fires on the transition into a matching state, not repeatedly while it stays there. This applies to ratio.reached: if the previous state already satisfied the conditions, the rule is skipped.

Execution log — every run is recorded in AutomationLog with a status (success / failed), the context, and a message. It is also mirrored to the audit log.

How it works

Idempotency on torrent.completed

A completion is an edge: the torrent crosses from incomplete to complete, and the rule fires. But what about a torrent that was already complete when you wrote the rule? It never crosses the edge, so it never fires — which is why "completed torrents keep seeding despite my delete rule" was a real and confusing bug.

It is fixed with a backfill: torrent.completed rules are re-run against already-complete torrents, using successful AutomationLog rows (keyed ruleId::hash) as a ledger of what has already been done. Failures are not recorded as done, so they retry on the next cycle.

Configuration

Triggers

Fourteen triggers exist, in three families.

TriggerCategoryFires when
torrent.completedtorrentA download completes (plus the backfill above).
ratio.reachedtorrentThe share ratio crosses your threshold. Rising-edge.
media.detectedmediaA new media file is scanned.
media.matchedmediaAn item is identified.
media.unmatchedmediaAn item could not be identified.
media.missing_artworkmediaAn item has no artwork.
media.missing_subtitlesmediaAn item has no preferred subtitles.
media.rename_completedmediaA rename/move completed.
media.server_refresh_failedmediaA media-server refresh failed.
rss.rule.created_for_inactive_showrssSomeone overrode the ended/canceled warning.
rss.show_status.changedrssA monitored show's airing status changed.
rss.show.became_activerssA monitored show came back.
rss.show.endedrssA monitored show ended.
rss.show.canceledrssA monitored show was canceled.

Condition operators

Exactly eight:

OperatorBehaviour
eq / neqStrict equality / inequality.
gt / gte / lt / lteNumeric comparison — both sides are coerced to numbers.
containsSubstring match on the stringified value.
matchesCase-insensitive regular expression. An invalid regex returns false rather than throwing — so a typo silently never matches.
An unknown operator returns false

Any operator that is not one of these eight evaluates to false, so the rule never fires. If a rule mysteriously never runs, check the operator first.

Actions

Eighteen actions exist, in four families.

Torrent actions (need a real torrent — only valid on the torrent triggers): move (param destination), pause, stop, delete, delete_with_data, rename_for_media (params preset, mode — default hardlinklibraryPath, template).

Context-free actions (valid on any trigger): webhook (POSTs JSON to params.url).

Media actions: media_scan_library, media_match, media_fetch_metadata, media_fetch_artwork, media_generate_nfo, media_rename, media_move, media_server_refresh.

RSS actions: refresh_rss_show_status, disable_rss_rule, convert_rule_to_backfill (turns off autoDownload — keep the rule, stop forward auto-grabbing).

Most triggers and actions are API-only today

The UI rule builder currently exposes only two triggers (torrent.completed, ratio.reached) and seven actions (move, pause, stop, delete, delete_with_data, webhook, rename_for_media), with condition fields limited to name, label, state, ratio, size, progress, downloadRate, uploadRate.

The other twelve triggers (all media.* and rss.*) and eleven actions (all media_* and rss_*) exist in the engine and are fully functional, but are reachable only through the REST APIPOST /api/automation/rules. The full live catalog is at GET /api/automation/catalog.

If you need them, create the rule via the API. It will run correctly; you just cannot author it in the form yet.

Event-context rules

The five rss.* triggers run through a separate path (evaluateEvent) that matches conditions against a plain event object rather than a torrent. Only event-safe actions are permitted there: webhook and the three rss_* actions. Any other action id is rejected with Action "<type>" is not valid for an event trigger and logged as failed.

Rule fields

FieldWhat it doesDefault
nameDisplay name.
descriptionFree text.
triggerThe one trigger.
conditionsThe ANDed condition array. Empty = always matches.[]
actionsThe ordered action array.[]
isEnabledWhether it runs.true
priorityHigher runs first.0

Endpoints

MethodPathPermission
GET/api/automation/catalogautomation.view
GET/api/automation/rulesautomation.view
POST/api/automation/rulesautomation.manage
PATCH/api/automation/rules/:idautomation.manage
DELETE/api/automation/rules/:idautomation.manage
GET/api/automation/rules/:id/logsautomation.view

Step-by-step walkthrough

1. Go to Automation → Automation Rules.

2. Create a rule that cannot do any harm first. Trigger torrent.completed, no conditions, no actions at all. Save it, enabled.

3. Complete a download. Open the rule's Execution log: a success run appears, with the torrent's context. You have now proved the trigger fires and the rule matches — which is the thing most people never actually verify before writing something destructive.

4. Add a condition. label eq movies. Complete a torrent with a different label. The rule should not run. Check the Execution log to confirm.

5. Now do something real. Add a move action with a destination, or a rename_for_media action. The execution log records the run either way, so you can see whether it actually succeeded.

6. Watch the execution log. Every run is recorded, success or failure. So is the audit log, under automation.rule.executed.

There is no dry-run

Automation has no test, simulate, or preview capability. A rule is either off or live.

So: build the rule with no actions first, watch the execution log to prove the trigger and conditions behave, and only then add the action that deletes things. There is no undo, and a rule with zero conditions matches everything.

Screenshots

Automation rules

New automation rule

Automation execution log

Watch this tutorial

Video coming soon.

Real-world examples

Stop seeding at a ratio target

Trigger: ratio.reached. Condition: ratio gte 2.0. Action: stop.

Because ratio.reached is rising-edge, this fires exactly once — on the transition past 2.0 — not on every 2-second sync tick for the rest of the torrent's life. If you also want the data gone, use delete_with_data instead of stop, but test with stop first.

Trigger: torrent.completed. Condition: label eq movies. Action: rename_for_media with preset: plex, mode: hardlink, and your movie libraryPath.

hardlink is the default mode for a reason: the file appears in the library and the original stays where the torrent client left it, so seeding continues. One copy of the bytes.

Convert an RSS rule to backfill when a show ends (API-only today)

Trigger: rss.show.ended. Action: convert_rule_to_backfill.

When the hourly show-status refresh notices a monitored show has ended, this turns off that rule's auto-download — you keep the rule and its history, but it stops forward-grabbing episodes that will never air. The action targets a rule either by explicit ruleId or by the show identity carried on the trigger's context.

Create this one via POST /api/automation/rules; the UI form does not yet offer rss.* triggers.

Fire a webhook into anything

Trigger: torrent.completed. Action: webhook with your URL. It POSTs a JSON body containing the torrent (or the event) and your params. That is your escape hatch for everything UltraTorrent does not do natively.

Troubleshooting

SymptomCauseFix
Completed torrents keep seeding despite a working delete ruleTwo separate historical bugs. (1) torrent.completed only fired on the completion edge, so a torrent that was already complete when the rule was written never triggered. (2) rTorrent's delete did not verify that removal actually happened. Both are fixed — there is now a backfill (using successful log rows as a ledger) and delete verifies + retries.Update. Then check the rule's Execution log — if the run shows success and the torrent is still there, it is an engine problem, not a rule problem.
A rule never firesMost likely an unknown operator (which evaluates to false), or an invalid regex in a matches condition (which also returns false rather than throwing). Or the trigger genuinely is not firing.Check the operator against the eight listed above. Then set the rule to zero conditions and no actions to prove the trigger itself fires.
A rule fires constantlyZero conditions means always matches. And only ratio.reached is rising-edge — other triggers fire every time the event occurs.Add conditions.
Only some of a rule's actions ranThe first action to throw aborts the rest of that rule.Read the Execution log — the failure message names what went wrong. Put non-destructive actions first.
Action "media_rename" is not valid for an event triggerEvent-context rules (the rss.* triggers) only permit webhook and the three rss_* actions.Use a torrent or media trigger for torrent/media actions.
I cannot find the media.* or rss.* triggers in the UIThe rule-builder form currently exposes only the two torrent triggers. The rest are engine-side and API-only.Create the rule via POST /api/automation/rules. See GET /api/automation/catalog for the full live list.
A numeric condition behaves oddlygt / gte / lt / lte coerce both sides to numbers. Comparing a non-numeric field numerically gives you NaN semantics.Compare numeric fields numerically; use eq / contains for strings.
Two rules conflictEvery matching rule runs; there is no stop-after-first-match. Ordering is by priority, descending.Use priority to sequence them, and make conditions mutually exclusive.

Best practices

  • Prototype with no actions. Prove the trigger and the conditions in the execution log before you attach anything destructive. There is no dry-run and no undo.
  • Put the harmless actions first. The first action to throw aborts the rest, so ordering decides how much of the rule ran.
  • Never leave a destructive rule with zero conditions. Zero conditions matches everything.
  • Use priority deliberately when rules could overlap.
  • Read the execution log after any change. It is the only feedback loop you have.
  • Prefer stop over delete_with_data until you are certain.
  • Remember ratio.reached is rising-edge — that is why it does not spam you.

Common mistakes

  • Writing a delete_with_data rule with no conditions and enabling it. This will delete everything, on completion, forever.
  • Assuming there is a dry-run. There is not.
  • Using an operator that does not exist (ne, regex, startsWith…) and concluding the engine is broken. Unknown operators silently evaluate to false.
  • Expecting every trigger to be rising-edge. Only ratio.reached is.
  • Trying to use a media action on an RSS trigger. Event-context rules reject it.
  • Looking for media.* triggers in the UI form. They are API-only for now.

FAQ

Can I test a rule before enabling it? No. There is no dry-run, simulate, or preview endpoint in the automation module. Build it with no actions and watch the execution log.

How often are torrent rules evaluated? On the torrent-sync loop, which runs every 2 seconds.

Do all matching rules run, or just the first? All of them, in order of priority descending.

What happens if an action fails? It aborts the remaining actions of that rule and the run is logged as failed, with the failure message. Other rules are unaffected.

Why does my ratio.reached rule only fire once? Because it is rising-edge — it fires on the transition into the matching state. If the previous state already satisfied the conditions, the rule is skipped. This is the desired behaviour, not a bug.

Is there a visual/drag-and-drop rule builder? Not today. The UI is a structured form — rule cards, a create dialog, condition rows, and action rows with per-type parameter widgets. There is no canvas.

Are rule runs audited? Yes. Every run writes automation.rule.executed to the audit log, with success or failure, the rule name, and the action list — in addition to the module's own execution log.

Checklist

  • Create a rule: torrent.completed, no conditions, no actions. Expected: a success run is logged on the next completion.
  • Add a label eq condition. Expected: it fires for matching labels only; the execution log shows nothing for others.
  • Deliberately use an unknown operator. Expected: the rule never fires — confirming the silent-false behaviour.
  • Add two actions to the rule. Expected: both run, in order.
  • Make the second action fail (a bad destination). Expected: the run logs failed, with the failure message naming what threw.
  • Check the audit log. Expected: an automation.rule.executed row with result: failure.
  • Create a ratio.reached rule and let a torrent pass the threshold. Expected: it fires exactly once, not repeatedly.

See also