Skip to content

Feat/add run automation once on finish - #1410

Closed
luckylittle wants to merge 5 commits into
autobrr:developfrom
luckylittle:feat/add-run-automation-once-on-finish
Closed

Feat/add run automation once on finish#1410
luckylittle wants to merge 5 commits into
autobrr:developfrom
luckylittle:feat/add-run-automation-once-on-finish

Conversation

@luckylittle

@luckylittle luckylittle commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

What is the relevant ticket?

What’s this PR do?

  • Added support for “run once on finish” rules using intervalSeconds = 0.

  • Validation now allows 0 (run once when the torrent finishes) or >= 60; anything in between is rejected.

  • Rules with intervalSeconds = 0:

    • Only run when the torrent reaches 100%.
    • Run once per torrent and do not track lastRuleRun.
    • Are always eligible (no interval timing checks).
  • UI update:

    • New “Run once on finish” checkbox next to “Run every”.
    • When checked, the interval dropdown is disabled and shows .
    • When unchecked, the dropdown is re-enabled and defaults back to 15 minutes.
  • Import/export fixes:

    • intervalSeconds = 0 is now correctly exported and imported.
    • Import validation now accepts 0 as a valid value.

Add
  • Run once on finish in automation

Where should the reviewer start?

  • internal/api/handlers/automations.go
  • internal/models/automation.go
  • internal/services/automations/processor.go
  • internal/services/automations/service.go
  • web/src/components/instances/preferences/WorkflowDialog.tsx
  • web/src/lib/workflow-utils.ts

How should this be manually tested?

  • ./qui serve

Risk involved?

  • Medium/High

Screenshots (if appropriate):

  • Unticked:
Screenshot from 2026-02-05 16-19-48
  • Ticked:
Screenshot from 2026-02-05 16-20-06

Does the documentation or dependencies need an update?

  • Doc comment needs to be updated to mention 0 (run once on finish).

Summary by CodeRabbit

  • New Features

    • Added "Run once on finish" option for automations, allowing workflows to execute a single time when a torrent completes rather than on a recurring interval.
    • Added UI checkbox control to easily toggle the new "run-once" behavior.
  • Bug Fixes

    • Improved validation for automation interval values to properly handle the new "run-once" configuration.

s0up4200 and others added 4 commits February 4, 2026 21:59
Signed-off-by: Lucian Maly <lucian@redhat.com>
Signed-off-by: Lucian Maly <lucian@redhat.com>
…nish" UI

Signed-off-by: Lucian Maly <lucian@redhat.com>
@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Walkthrough

This pull request adds a "run once on finish" feature to automations, allowing rules to execute immediately when torrents reach 100% completion. IntervalSeconds value of 0 now indicates this behavior across the API, backend services, and frontend UI, with corresponding validation and processing logic updates.

Changes

Cohort / File(s) Summary
Backend Models & API
internal/api/handlers/automations.go, internal/models/automation.go
Updated AutomationPayload and Automation structs to support IntervalSeconds=0 semantics for "run once on finish." Added validation to allow 0 or values ≥60 seconds.
Backend Automation Services
internal/services/automations/processor.go, internal/services/automations/service.go
Modified rule matching to restrict IntervalSeconds=0 rules to torrents with 100% progress. Exempted run-once rules from interval throttling and cadence tracking.
Frontend UI Dialog
web/src/components/instances/preferences/WorkflowDialog.tsx
Added checkbox control for "Run once on finish" feature, allowing users to toggle between standard intervals and run-once behavior. Updated dropdown to reflect run-once state visually.
Frontend Utilities
web/src/lib/workflow-utils.ts
Updated import/export format handlers to properly serialize/deserialize IntervalSeconds=0 values for run-once rules.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as WorkflowDialog
    participant API as API Handler
    participant Service as Automation Service
    participant Processor as Rule Processor
    participant DB as Torrent Data

    User->>UI: Check "Run once on finish"
    UI->>UI: Set intervalSeconds = 0
    UI->>API: POST automation config
    API->>API: Validate intervalSeconds (0 allowed)
    API->>Service: Store automation rule
    Service->>Service: Mark as run-once (interval=0)
    
    Note over Processor: On torrent progress update
    DB->>Processor: Check matching rules
    Processor->>Processor: Filter by intervalSeconds
    alt IntervalSeconds == 0
        Processor->>DB: Check torrent progress
        alt progress >= 100%
            Processor->>Processor: Match rule
            Processor->>Service: Execute action once
            Service->>Service: Do NOT update cadence
        else progress < 100%
            Processor->>Processor: Skip rule
        end
    else IntervalSeconds > 0
        Processor->>Service: Check interval throttling
        Processor->>Processor: Match if eligible
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #926 — Also modifies automation matching logic with progress gating for Delete rules, directly related to progress-based rule filtering changes in this PR.

Suggested labels

enhancement, automations, web, backend, api, area/backend, area/frontend

Suggested reviewers

  • s0up4200

Poem

🐰 When torrents finish their final byte,
A rule can whisper, "Run me right!"
Zero seconds—once and done,
No looping loops when progress = 1. ✨
Automation's friend, both fleet and true!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature addition—supporting automation rules that run once when torrents finish—and is directly related to all the substantial changes across both backend and UI components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/services/automations/processor.go (1)

94-114: ⚠️ Potential issue | 🟡 Minor

Preallocate matching to satisfy lint and reduce allocations.
Line 98 currently triggers the prealloc lint failure; preallocating fixes the pipeline warning.

💡 Suggested fix
-	var matching []*models.Automation
+	matching := make([]*models.Automation, 0, len(rules))
🤖 Fix all issues with AI agents
In `@web/src/lib/workflow-utils.ts`:
- Around line 177-179: The code currently copies obj.intervalSeconds into
data.intervalSeconds only for valid values and silently ignores invalid numbers
(e.g., 1–59); update the validation so that when obj.intervalSeconds is present
but not (=== 0 || >= 60) you reject it (throw or return a validation error)
instead of skipping it. Locate the block that reads obj.intervalSeconds and
change it to: if typeof obj.intervalSeconds === "number" and
(obj.intervalSeconds === 0 || obj.intervalSeconds >= 60) set
data.intervalSeconds = obj.intervalSeconds; else if obj.intervalSeconds is
defined but invalid, raise a clear validation error referencing
obj.intervalSeconds so callers know the value is out of allowed range.

Comment on lines +177 to 179
// Optional intervalSeconds: 0 = run once on finish, otherwise must be >= 60
if (typeof obj.intervalSeconds === "number" && (obj.intervalSeconds === 0 || obj.intervalSeconds >= 60)) {
data.intervalSeconds = obj.intervalSeconds

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject invalid intervalSeconds instead of silently ignoring.
Line 177 only includes valid values but doesn’t error on invalid ones (e.g., 1–59). Since the comment says “must be >= 60,” returning an error keeps import validation consistent with backend rules.

💡 Suggested fix
-  if (typeof obj.intervalSeconds === "number" && (obj.intervalSeconds === 0 || obj.intervalSeconds >= 60)) {
-    data.intervalSeconds = obj.intervalSeconds
-  }
+  if (obj.intervalSeconds !== undefined) {
+    if (typeof obj.intervalSeconds !== "number" || Number.isNaN(obj.intervalSeconds) || (obj.intervalSeconds !== 0 && obj.intervalSeconds < 60)) {
+      return { data: null, error: "intervalSeconds must be 0 (run once on finish) or at least 60" }
+    }
+    data.intervalSeconds = obj.intervalSeconds
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Optional intervalSeconds: 0 = run once on finish, otherwise must be >= 60
if (typeof obj.intervalSeconds === "number" && (obj.intervalSeconds === 0 || obj.intervalSeconds >= 60)) {
data.intervalSeconds = obj.intervalSeconds
// Optional intervalSeconds: 0 = run once on finish, otherwise must be >= 60
if (obj.intervalSeconds !== undefined) {
if (typeof obj.intervalSeconds !== "number" || Number.isNaN(obj.intervalSeconds) || (obj.intervalSeconds !== 0 && obj.intervalSeconds < 60)) {
return { data: null, error: "intervalSeconds must be 0 (run once on finish) or at least 60" }
}
data.intervalSeconds = obj.intervalSeconds
}
🤖 Prompt for AI Agents
In `@web/src/lib/workflow-utils.ts` around lines 177 - 179, The code currently
copies obj.intervalSeconds into data.intervalSeconds only for valid values and
silently ignores invalid numbers (e.g., 1–59); update the validation so that
when obj.intervalSeconds is present but not (=== 0 || >= 60) you reject it
(throw or return a validation error) instead of skipping it. Locate the block
that reads obj.intervalSeconds and change it to: if typeof obj.intervalSeconds
=== "number" and (obj.intervalSeconds === 0 || obj.intervalSeconds >= 60) set
data.intervalSeconds = obj.intervalSeconds; else if obj.intervalSeconds is
defined but invalid, raise a clear validation error referencing
obj.intervalSeconds so callers know the value is out of allowed range.

@jabloink

jabloink commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

@luckylittle Is the end goal of this running the workflow on the torrent that triggered it or does the workflow running on all torrents when any torrent finishes fit your usecase?

Edit:
It also appears that currently this will run the workflow on all torrents that are complete every 20 seconds, rather than only running once when a torrent completes, you mentioned you had just started working on it but its not marked as draft so just double checking

@luckylittle
luckylittle marked this pull request as draft February 6, 2026 00:11
@luckylittle

Copy link
Copy Markdown
Contributor Author

@jabloink
CC: @0rkag @s0up4200

@luckylittle Is the end goal of this running the workflow on the torrent that triggered it or does the workflow running on all torrents when any torrent finishes fit your usecase?

The goal is identical to what qBt does - run a script for each torrent that completed, but as part of the workflow so you can trigger different script for different conditions:

Screenshot from 2026-02-06 11-08-09

[screenshot from qBt]

I think this will require checking how qBittorrent reports completion and whether a completion callback exists and implement that in https://github.com/autobrr/go-qbittorrent ?

Edit: It also appears that currently this will run the workflow on all torrents that are complete every 20 seconds, rather than only running once when a torrent completes, you mentioned you had just started working on it but its not marked as draft so just double checking

Moved to draft for now.

@luckylittle

Copy link
Copy Markdown
Contributor Author

Related: autobrr/go-qbittorrent#104

@zze0s

zze0s commented Feb 6, 2026

Copy link
Copy Markdown
Collaborator

Polling for this is not really ideal.

A better solution to this would be make the qbit instances set a script in on complete that sends a webhook to Qui when a torrent is completed.

There was another PR about this or maybe it was a discussion on Discord. I'll try to find it.

Edit: found it:

This PR: #338
And discussion here: https://discord.com/channels/881212911849209957/1420877833806549073

@s0up4200 s0up4200 closed this May 19, 2026
@luckylittle
luckylittle deleted the feat/add-run-automation-once-on-finish branch June 9, 2026 08:53
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.

4 participants