Skip to content

feat(automations): add native webhook action - #1733

Draft
nitrobass24 wants to merge 3 commits into
developfrom
feat/automations-webhook-action
Draft

feat(automations): add native webhook action#1733
nitrobass24 wants to merge 3 commits into
developfrom
feat/automations-webhook-action

Conversation

@nitrobass24

@nitrobass24 nitrobass24 commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a first-class Webhook automation action that POSTs a structured JSON payload to a user-configured HTTP endpoint when an automation rule matches
  • Replaces the need for the External Program + shell script + curl workaround
  • No database migration needed — webhook config is stored in the existing conditions JSON column

Design decisions

  • Always POST with an auto-built JSON payload containing full torrent context (name, hash, category, tags, tracker, paths, size, ratio, state, progress, seeds/leeches, rule ID/name, instance ID/name, timestamp)
  • Optional custom headers for auth (Authorization, X-API-Key, etc.)
  • 30s timeout, fire-and-forget async execution (same pattern as external program)
  • Per-run cap of 50 webhooks to prevent flooding
  • Last-rule-wins semantics when multiple rules match
  • No conflict with PR feat(notifications): add webhook:// notification target #1623 — that adds webhook:// notification targets in the notifications package; this adds a per-workflow automation action in an entirely separate package

Changes

Backend

  • internal/models/automation.goWebhookAction, WebhookHeader structs with Validate(), added to ActionConditions and IsEmpty()
  • internal/models/automation_activity.goActivityActionWebhook constant
  • internal/services/automations/processor.go — Webhook fields in torrentDesiredState, condition evaluation, stats tracking
  • internal/services/automations/service.gowebhookPayload struct, executeWebhooksFromAutomation (with 50/run cap), HTTP execution with activity logging
  • internal/services/automations/grouping.goconditionFromWebhookAction
  • internal/api/handlers/automations.go — Validation: delete standalone check, URL/header validation, condition field/regex checks

Frontend

  • web/src/types/index.tsWebhookAction, WebhookHeader interfaces
  • web/src/components/instances/preferences/WorkflowDialog.tsx — Webhook action type, form state, UI section (URL + dynamic headers), validation
  • web/src/components/instances/preferences/WorkflowsOverview.tsx — Webhook badge, color, label
  • web/src/components/instances/preferences/WorkflowsPanel.tsx — Webhook badge with URL tooltip
  • web/src/components/instances/preferences/AutomationActivityRunDialog.tsx — Webhook activity label

Tests

  • WebhookAction.Validate() — 8 test cases (nil, disabled, valid URLs, bad scheme, empty headers)
  • Processor tests — condition met/not met, last-rule-wins, disabled, hasActions
  • TestDeleteStandaloneWithWebhook — delete + webhook rejected

Test plan

  • make lint passes (Go lint clean; frontend lint warnings are pre-existing)
  • make test passes (-race -count=3)
  • make build succeeds
  • Create automation with webhook action → verify POST fires on match
  • Verify activity log shows success/failure with HTTP status
  • Verify dry-run records webhook without firing
  • Verify delete + webhook combination is rejected at save time
  • Verify invalid URL / empty header key caught at save time

Closes #1624

Summary by CodeRabbit

  • New Features

    • Webhook automation action: configure URL, optional headers, and rule condition; UI to add/edit webhooks; badges and labels in workflows and activity dialogs.
    • Webhooks included in dry-run summaries and recorded as automation activities; live webhook calls executed asynchronously.
  • Bug Fixes / Validation

    • Prevents combining standalone delete with an enabled webhook; invalid webhook configs return clear validation errors.
  • Behavior

    • “Last matching rule wins” for webhook selection; per-run webhook execution capped with truncation warnings.

Add a first-class Webhook automation action that POSTs a structured JSON
payload to a user-configured HTTP endpoint when an automation rule matches.

- Always POST with auto-built JSON payload (torrent name, hash, category,
  tags, tracker, paths, size, ratio, state, progress, seeds/leeches,
  rule ID/name, instance ID/name, timestamp)
- Optional custom headers for auth (Authorization, X-API-Key, etc.)
- 30s timeout, fire-and-forget async execution (like external program)
- Per-run cap of 50 webhooks to prevent flooding
- Last-rule-wins semantics when multiple rules match
- Activity log entries with success/failure outcome
- Full validation: URL scheme, empty headers, delete standalone check
- Frontend: webhook UI in workflow dialog, badges in rule cards/panels,
  activity run display

Closes #1624
@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a native Webhook automation action: model/types and validation, integrates webhook evaluation into rule processing (last-rule-wins), records dry-run activities, executes HTTP POSTs (capped, async) with activity logging, and updates frontend UI/types for configuring and displaying webhooks.

Changes

Cohort / File(s) Summary
Backend Models
internal/models/automation.go, internal/models/automation_activity.go
Add WebhookAction/WebhookHeader types, WebhookAction.Validate(), attach webhook to ActionConditions, update IsEmpty(), and add ActivityActionWebhook constant.
Validation & Handlers
internal/api/handlers/automations.go, internal/api/handlers/automations_validation_test.go
Treat enabled webhook as an “other action” (prevents Delete+Webhook), invoke webhook validation (Validate()), and add tests for webhook validation and delete+webhook combination.
Processor & Grouping
internal/services/automations/processor.go, internal/services/automations/processor_test.go, internal/services/automations/grouping.go
Evaluate webhook conditions during rule processing (last-rule-wins), store webhookAction and rule metadata in desired state, add counters/stats, include webhook conditions in grouping trees, and add unit tests.
Service Execution & Tests
internal/services/automations/service.go, internal/services/automations/service_test.go
Introduce webhookClient, collect pending webhook executions, record dry-run webhook activities, execute HTTP POSTs (sorted + capped per run), log per-torrent webhook activities, add payload/types/helpers, and adjust tests to include new param.
Frontend UI & Types
web/src/components/instances/preferences/WorkflowDialog.tsx, WorkflowsOverview.tsx, WorkflowsPanel.tsx, AutomationActivityRunDialog.tsx, web/src/types/index.ts
Add webhook to action union/types, UI panel for URL/headers, validation (http/https + hostname), hydrate/save fields, display webhook labels/badges, and include webhook in enabled-actions logic.

Sequence Diagram

sequenceDiagram
    participant Rule as Automation Rule
    participant Processor as Rule Processor
    participant Service as Automation Service
    participant HTTP as HTTP Client
    participant Logger as Activity Logger

    Rule->>Processor: evaluateConditions(rule, torrent)
    Processor->>Processor: check webhook condition (enabled + URL)
    alt condition matches
        Processor->>Service: enqueue pendingWebhookExec(rule, action, torrent)
    end

    Service->>Service: executeWebhooksFromAutomation(pendingExecutions)
    loop per webhook (deterministic sort + cap)
        Service->>HTTP: POST action.URL with JSON payload + headers
        HTTP-->>Service: response (status)
        alt 2xx
            Service->>Logger: logWebhookActivity(success, details)
        else
            Service->>Logger: logWebhookActivity(failure, details)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, backend, web, automations, api

Suggested reviewers

  • s0up4200

Poem

🐇 I hop and I code, a tiny webhook I bring,
When rules align true, I make an endpoint sing,
A POST with payload, headers neat and trim,
Dry-run then fire—async, fleet and prim,
—cheers from a curious rabbit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(automations): add native webhook action' directly and concisely describes the main change: adding a webhook action feature to automations.
Linked Issues check ✅ Passed The PR implements the core requirements from #1624: native webhook action with URL/headers support, timeout (30s), async execution, activity logging, and prevents delete+webhook combination. Templating and HTTP method configurability were requested but not implemented.
Out of Scope Changes check ✅ Passed All changes are scoped to webhook action implementation: backend structures, validation, processor logic, service execution, and frontend UI/types. No unrelated refactoring or feature work is present.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/automations-webhook-action

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: 4

Caution

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

⚠️ Outside diff range comments (4)
web/src/components/instances/preferences/WorkflowDialog.tsx (3)

1513-1526: ⚠️ Potential issue | 🟠 Major

webhook is missing from enabled-action counting.

Line [1513] builds enabledActionsCount but omits formState.webhookEnabled. Webhook-only rules are treated as “no action” (blocks dry-run/save and keeps wrong action-picker state).

Suggested fix
   const enabledActionsCount = [
     formState.speedLimitsEnabled,
     formState.shareLimitsEnabled,
     formState.pauseEnabled,
     formState.resumeEnabled,
     formState.recheckEnabled,
     formState.reannounceEnabled,
     formState.autoManagementEnabled,
     formState.deleteEnabled,
     formState.tagEnabled,
     formState.categoryEnabled,
     formState.moveEnabled,
     formState.externalProgramEnabled,
+    formState.webhookEnabled,
   ].filter(Boolean).length
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/instances/preferences/WorkflowDialog.tsx` around lines
1513 - 1526, The enabledActionsCount array used to compute how many actions are
turned on (symbol: enabledActionsCount) omits the webhook flag, so rules that
only enable webhooks are treated as having no actions; include
formState.webhookEnabled in the array alongside the other flags (e.g.,
formState.deleteEnabled, formState.externalProgramEnabled) so the count and
subsequent logic (dry-run/save gating and action-picker state) correctly
recognize webhook-only rules.

990-1003: ⚠️ Potential issue | 🟠 Major

Webhook condition is not hydrated into shared actionCondition.

Lines [990-1003] don’t include conditions.webhook?.condition, so editing a webhook-only rule can silently drop its condition on re-save.

Suggested fix
           actionCondition = conditions.speedLimits?.condition
             ?? conditions.shareLimits?.condition
             ?? conditions.pause?.condition
             ?? conditions.resume?.condition
             ?? conditions.recheck?.condition
             ?? conditions.reannounce?.condition
             ?? conditions.autoManagement?.condition
             ?? conditions.delete?.condition
             ?? conditions.tags?.[0]?.condition
             ?? conditions.tag?.condition
             ?? conditions.category?.condition
             ?? conditions.move?.condition
             ?? conditions.externalProgram?.condition
+            ?? conditions.webhook?.condition
             ?? null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/instances/preferences/WorkflowDialog.tsx` around lines 990
- 1003, The shared actionCondition assignment in WorkflowDialog.tsx omits
conditions.webhook?.condition, causing webhook-only rules to lose their
condition on save; update the actionCondition expression (the variable named
actionCondition) to include conditions.webhook?.condition in the
nullish-coalescing chain (placing it alongside the other conditions like
conditions.tags, conditions.tag, conditions.category, etc.) so webhook
conditions are properly hydrated before save.

203-215: ⚠️ Potential issue | 🟡 Minor

Dry-run summary omits webhook action text.

webhook is in dry-run labels, but not in the impacted-summary cases (Lines [203-215]), so webhook events fall back to generic “Dry-run completed”.

Suggested fix
     case "external_program":
+    case "webhook":
     case "deleted_ratio":
     case "deleted_seeding":
     case "deleted_unregistered":
     case "deleted_condition": {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/instances/preferences/WorkflowDialog.tsx` around lines 203
- 215, The dry-run summary switch in WorkflowDialog.tsx currently groups several
status strings (cases "paused", "resumed", "rechecked", ... "deleted_condition")
to return an impacted-count string but omits "webhook", causing webhook events
to fall back to the generic message; update the switch that builds the
impacted-summary (the case block using details?.count) to include "webhook"
among the case labels so webhook dry-run events return the same `${count}
torrent(s) impacted` output (using the existing details?.count logic).
internal/services/automations/service.go (1)

2209-2210: ⚠️ Potential issue | 🟠 Major

Cap a deterministic webhook set before dry-run/live split.

webhookExecutions is built while ranging over states, which is a Go map. Once the live path trims that slice to 50, the surviving executions are effectively random from run to run. The dry-run path then logs all matches instead of the capped subset, so preview diverges from live behavior as soon as the limit is exceeded. Apply the same cap to a stable order before both dry-run activity creation and live dispatch.

Also applies to: 2619-2628, 5473-5483, 6094-6100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/services/automations/service.go` around lines 2209 - 2210, The
webhookExecutions slice is populated by iterating over the map variable states
(using torrentByHash[hash]), causing nondeterministic order and making the live
path's truncation to 50 effectively random and the dry-run preview inconsistent;
fix by collecting executions into webhookExecutions, then sort them
deterministically (e.g., by a stable key such as hash, webhook ID, or timestamp)
and apply the same cap (slice to 50) to the sorted list before doing dry-run
activity creation or live dispatch so both paths operate on the identical
deterministic subset; apply the same pattern to the other occurrences that build
webhookExecutions (the blocks mentioned around the other ranges).
🧹 Nitpick comments (1)
internal/api/handlers/automations.go (1)

1052-1054: Inconsistent gating on Enabled flag for regex validation.

Unlike conditionTreesForValidation (lines 641-643) which gates on conditions.Webhook.Enabled, this regex validation block only checks conditions.Webhook != nil. This means regex patterns in disabled webhooks will be validated, while their condition trees won't be included for grouping-ID validation.

For consistency with other validation paths and to avoid validating regexes in disabled actions, consider adding the Enabled check:

🔧 Suggested fix for consistency
-	if conditions.Webhook != nil {
+	if conditions.Webhook != nil && conditions.Webhook.Enabled {
 		validateConditionRegex(conditions.Webhook.Condition, "/conditions/webhook/condition", &result)
 	}

Note: The current behavior is arguably defensive (catching invalid regexes before enabling), so this is optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/handlers/automations.go` around lines 1052 - 1054, The regex
validation for webhook conditions currently runs if conditions.Webhook != nil
but other validation paths (e.g., conditionTreesForValidation) gate on
conditions.Webhook.Enabled; update the check around the validateConditionRegex
call so it only runs when conditions.Webhook is non-nil and
conditions.Webhook.Enabled is true (i.e., mirror the gating used for
conditionTreesForValidation) to keep regex validation consistent with disabled
actions; locate the call to validateConditionRegex and the conditions.Webhook
reference and add the Enabled check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/models/automation.go`:
- Around line 1015-1041: AutomationStore.Create and AutomationStore.Update
currently only call ExternalProgram.Validate() and therefore skip the new
WebhookAction.Validate(); update both Create and Update to invoke
automation.Conditions.Webhook.Validate() (or call WebhookAction.Validate() on
the stored object) when a webhook action is present, and if it returns an error
wrap it with contextual text (e.g., "invalid webhook action: %w") and return it
so invalid webhook URLs/headers are rejected at store write time; ensure you
perform the same check in both AutomationStore.Create and AutomationStore.Update
alongside the existing ExternalProgram.Validate() call.

In `@internal/services/automations/service.go`:
- Around line 6089-6114: The instance lookup currently uses context.Background()
so it ignores the caller timeout; update the function signature
executeWebhooksFromAutomation(_ context.Context, ...) to accept and use a real
context (e.g., ctx context.Context) and call s.instanceStore.Get(ctx,
instanceID) instead of context.Background(); keep the existing nil check for
s.instanceStore and assignment to instanceName but ensure you pass ctx into
instanceStore.Get so the lookup respects the caller timeout (no other behavioral
changes).

In `@web/src/components/instances/preferences/WorkflowDialog.tsx`:
- Line 1424: The headers array is only filtered for empty keys but not
normalized, so whitespace in header names/values can slip into the submitted
payload; in the WorkflowDialog serialization where you build headers from
input.exprWebhookHeaders, first map each header to trimmed key/value (e.g.,
transform each h to { key: h.key.trim(), value: h.value.trim() }), then filter
out entries with empty keys, and assign that result to headers so both keys and
values are normalized before submit.
- Around line 2004-2006: The frontend currently uses new
URL(submitState.exprWebhookUrl.trim()) in WorkflowDialog.tsx which accepts
non-HTTP schemes; update the validation to parse the URL (using new URL(...))
then explicitly verify that url.protocol is "http:" or "https:" and that
url.host (or url.hostname) is non-empty before accepting it, and surface a
validation error if those checks fail so the client-side contract matches the
backend's http/https + non-empty host requirement for
submitState.exprWebhookUrl.

---

Outside diff comments:
In `@internal/services/automations/service.go`:
- Around line 2209-2210: The webhookExecutions slice is populated by iterating
over the map variable states (using torrentByHash[hash]), causing
nondeterministic order and making the live path's truncation to 50 effectively
random and the dry-run preview inconsistent; fix by collecting executions into
webhookExecutions, then sort them deterministically (e.g., by a stable key such
as hash, webhook ID, or timestamp) and apply the same cap (slice to 50) to the
sorted list before doing dry-run activity creation or live dispatch so both
paths operate on the identical deterministic subset; apply the same pattern to
the other occurrences that build webhookExecutions (the blocks mentioned around
the other ranges).

In `@web/src/components/instances/preferences/WorkflowDialog.tsx`:
- Around line 1513-1526: The enabledActionsCount array used to compute how many
actions are turned on (symbol: enabledActionsCount) omits the webhook flag, so
rules that only enable webhooks are treated as having no actions; include
formState.webhookEnabled in the array alongside the other flags (e.g.,
formState.deleteEnabled, formState.externalProgramEnabled) so the count and
subsequent logic (dry-run/save gating and action-picker state) correctly
recognize webhook-only rules.
- Around line 990-1003: The shared actionCondition assignment in
WorkflowDialog.tsx omits conditions.webhook?.condition, causing webhook-only
rules to lose their condition on save; update the actionCondition expression
(the variable named actionCondition) to include conditions.webhook?.condition in
the nullish-coalescing chain (placing it alongside the other conditions like
conditions.tags, conditions.tag, conditions.category, etc.) so webhook
conditions are properly hydrated before save.
- Around line 203-215: The dry-run summary switch in WorkflowDialog.tsx
currently groups several status strings (cases "paused", "resumed", "rechecked",
... "deleted_condition") to return an impacted-count string but omits "webhook",
causing webhook events to fall back to the generic message; update the switch
that builds the impacted-summary (the case block using details?.count) to
include "webhook" among the case labels so webhook dry-run events return the
same `${count} torrent(s) impacted` output (using the existing details?.count
logic).

---

Nitpick comments:
In `@internal/api/handlers/automations.go`:
- Around line 1052-1054: The regex validation for webhook conditions currently
runs if conditions.Webhook != nil but other validation paths (e.g.,
conditionTreesForValidation) gate on conditions.Webhook.Enabled; update the
check around the validateConditionRegex call so it only runs when
conditions.Webhook is non-nil and conditions.Webhook.Enabled is true (i.e.,
mirror the gating used for conditionTreesForValidation) to keep regex validation
consistent with disabled actions; locate the call to validateConditionRegex and
the conditions.Webhook reference and add the Enabled check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2ced714d-9055-4d94-937e-dc9a852991c5

📥 Commits

Reviewing files that changed from the base of the PR and between 3fbcc7a and 6508309.

📒 Files selected for processing (14)
  • internal/api/handlers/automations.go
  • internal/api/handlers/automations_validation_test.go
  • internal/models/automation.go
  • internal/models/automation_activity.go
  • internal/services/automations/grouping.go
  • internal/services/automations/processor.go
  • internal/services/automations/processor_test.go
  • internal/services/automations/service.go
  • internal/services/automations/service_test.go
  • web/src/components/instances/preferences/AutomationActivityRunDialog.tsx
  • web/src/components/instances/preferences/WorkflowDialog.tsx
  • web/src/components/instances/preferences/WorkflowsOverview.tsx
  • web/src/components/instances/preferences/WorkflowsPanel.tsx
  • web/src/types/index.ts

Comment thread internal/models/automation.go
Comment thread internal/services/automations/service.go Outdated
Comment thread web/src/components/instances/preferences/WorkflowDialog.tsx Outdated
Comment thread web/src/components/instances/preferences/WorkflowDialog.tsx
- Add Webhook.Validate() to AutomationStore.Create and Update
- Use caller context for instance lookup in executeWebhooksFromAutomation
- Trim header keys/values before submit in WorkflowDialog
- Validate http/https scheme and non-empty host in frontend URL check
- Sort webhook executions by hash for deterministic truncation
- Include webhookEnabled in enabledActionsCount array
- Add webhook?.condition to actionCondition hydration chain
- Add "webhook" case to dry-run summary switch
- Gate webhook regex validation on Enabled flag
@nitrobass24

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/models/automation.go`:
- Around line 1042-1045: The current loop over a.Headers only checks for empty
keys; replace that with a validation that trims the key and enforces HTTP token
syntax: implement a helper isValidHTTPHeaderName(name string) that returns false
for names containing spaces, control characters, or characters outside token set
(ALPHA / DIGIT / "!#$%&'*+-.^_`|~"), call it from the for i, h := range
a.Headers loop after strings.TrimSpace(h.Key), and if validation fails return
fmt.Errorf("webhook header %d has invalid name %q", i, key) so
invalid/header-with-spaces or control chars are rejected early.

In `@internal/services/automations/service.go`:
- Around line 6194-6198: The code currently persists remote response bodies into
AutomationActivity.Reason in the else branch of the webhook call; update the
failure logging in the else branch (the s.logWebhookActivity call that uses
models.ActivityOutcomeFailed and fmt.Sprintf("HTTP %d: %s", ...)) to stop
including the response body and instead record only the HTTP status (e.g.,
fmt.Sprintf("HTTP %d", resp.StatusCode)); this change should be applied where
s.logWebhookActivity is invoked for failures using instanceID, torrent, ruleID,
ruleName and models.ActivityOutcomeFailed so no remote body/PII is stored.
- Around line 5473-5483: The dry-run activity currently reports all
webhookExecutions instead of mirroring the live execution's sorting and
truncation to maxWebhooksPerRun; modify the block that builds uniqueHashes
(around webhookExecutions, dedupeHashes, createActivity,
buildRunItemsFromHashes) to first sort webhookExecutions the same way the live
run does and then truncate the list to maxWebhooksPerRun before extracting
hashes and deduping, so the activity count and buildRunItemsFromHashes output
match real execution.
- Line 571: The webhookClient currently uses a default http.Client which follows
redirects; update the webhookClient instantiation (webhookClient:
&http.Client{...}) to disable automatic redirects by setting CheckRedirect to a
function that returns http.ErrUseLastResponse so 3xx responses are returned to
the caller instead of being followed; keep the existing Timeout field intact.

In `@web/src/components/instances/preferences/WorkflowDialog.tsx`:
- Around line 1741-1744: Create a shared URL validation helper (e.g.,
validateWebhookUrl or parseWebhookUrl) that runs the same checks as the
submit-time new URL(...) guard (trimmed non-empty, valid URL, allowed scheme and
host extraction) and return parsed parts or an error; replace the inline
presence check on dryRunInput.exprWebhookUrl and call this helper from the
dry-run path, from live-preview/enable-preview handlers, and from the
save/submit flow so all three paths gate on the same validation before building
webhook payloads to avoid premature backend validation errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 75eaee51-91f3-4fbc-9ba1-5ac655654192

📥 Commits

Reviewing files that changed from the base of the PR and between 6508309 and 879b805.

📒 Files selected for processing (4)
  • internal/api/handlers/automations.go
  • internal/models/automation.go
  • internal/services/automations/service.go
  • web/src/components/instances/preferences/WorkflowDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/api/handlers/automations.go

Comment thread internal/models/automation.go
Comment thread internal/services/automations/service.go Outdated
Comment thread internal/services/automations/service.go Outdated
Comment thread internal/services/automations/service.go Outdated
Comment thread web/src/components/instances/preferences/WorkflowDialog.tsx
- Stop persisting remote response bodies in activity log, record HTTP
  status code only
- Mirror live sort+truncation in dry-run webhook activity recording
- Disable automatic redirect following on webhook HTTP client

@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

🧹 Nitpick comments (1)
internal/services/automations/service.go (1)

3663-3664: Confirm whether webhook actions should participate in Notify summaries.

notifyAutomationSummary(...) still runs synchronously at the end of applyRulesForInstance, but webhook outcomes are only recorded later from background goroutines. As written, webhook-only rules produce no summary notification at all, and mixed runs omit webhook failures from that summary. If that is intentional, ignore this; otherwise the summary needs to move behind webhook completion or pre-account for webhook dispatches.

Also applies to: 6097-6137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/services/automations/service.go` around lines 3663 - 3664, The
current flow calls s.executeWebhooksFromAutomation(...) asynchronously so
webhook results aren't included in notifyAutomationSummary(...) called at the
end of applyRulesForInstance, causing webhook-only rules to be omitted and
webhook failures to be missing from mixed summaries; either (A) make
s.executeWebhooksFromAutomation join/wait for webhook execution completion (or
return a future/result channel) and invoke notifyAutomationSummary(...) after
those results are available, or (B) change applyRulesForInstance to pre-record
webhook dispatch entries (with pending state) into the summary and have the
background webhook goroutines update/patch the same summary when they finish;
update functions s.executeWebhooksFromAutomation, applyRulesForInstance, and
notifyAutomationSummary to propagate and consume webhook outcome information
accordingly so summaries include webhook results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/services/automations/service.go`:
- Around line 6214-6223: The async activity insert using s.activityStore.Create
(creating a models.AutomationActivity with InstanceID, Hash, TorrentName,
Action, RuleID, RuleName, Outcome, Reason) is unbounded; wrap the Create call in
a short timeout context (e.g., context.WithTimeout of a few seconds) and use
that context for the Create so the goroutine can’t block indefinitely on a slow
store; ensure you cancel the context after use and keep this change only on the
fire-and-forget path so normal synchronous behavior is unchanged.

---

Nitpick comments:
In `@internal/services/automations/service.go`:
- Around line 3663-3664: The current flow calls
s.executeWebhooksFromAutomation(...) asynchronously so webhook results aren't
included in notifyAutomationSummary(...) called at the end of
applyRulesForInstance, causing webhook-only rules to be omitted and webhook
failures to be missing from mixed summaries; either (A) make
s.executeWebhooksFromAutomation join/wait for webhook execution completion (or
return a future/result channel) and invoke notifyAutomationSummary(...) after
those results are available, or (B) change applyRulesForInstance to pre-record
webhook dispatch entries (with pending state) into the summary and have the
background webhook goroutines update/patch the same summary when they finish;
update functions s.executeWebhooksFromAutomation, applyRulesForInstance, and
notifyAutomationSummary to propagate and consume webhook outcome information
accordingly so summaries include webhook results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 86eaa007-cf75-4415-b862-a14b877b8dee

📥 Commits

Reviewing files that changed from the base of the PR and between 879b805 and 1be48fe.

📒 Files selected for processing (1)
  • internal/services/automations/service.go

Comment thread internal/services/automations/service.go
@nitrobass24 nitrobass24 self-assigned this Apr 9, 2026
@nitrobass24 nitrobass24 added enhancement New feature or request automations labels Apr 9, 2026
@nitrobass24
nitrobass24 marked this pull request as draft May 14, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automations enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add native webhook action for automations

1 participant