The "Remove upload markup from deleted posts" automation was incorrectly
processing posts that were deleted but still had pending reviewables in
the review queue.
Scenario: When a post is flagged (hidden from public) and the author
subsequently deletes it, the post has both `hidden: true` and
`deleted_at` set. The automation would strip upload markup from these
posts, causing data loss when moderators later restored them after
completing their review.
This fix adds a NOT EXISTS clause to exclude any post with a pending
reviewable targeting it. The automation will now wait until the review
is resolved (approved/rejected/ignored) before processing the post.
When automations fail silently (e.g., PM sending blocked due to
validation errors), admins have no visibility into what's going wrong.
This leaves them guessing why their automations aren't working as
expected.
This change addresses the problem in three ways:
1. Error tracking in automation stats
The Stat model now tracks `total_errors` alongside existing run metrics.
When an automation raises an exception during execution, it's caught and
counted. This gives admins visibility into which automations are
experiencing problems.
2. Centralized logging module
Created `DiscourseAutomation::Logger` to replace scattered
`Rails.logger.warn("[discourse-automation] ...")` calls throughout the
codebase. This eliminates the fragile manual prefix pattern and ensures
consistent log formatting across all automation code. All existing
logging calls have been migrated to use this module.
3. Error count display in admin UI
The automation list now shows recent error counts next to run
statistics. Clicking the error count links directly to `/logs` filtered
for discourse-automation entries, making it easy to investigate
failures. The link uses `data-auto-route="true"` to ensure proper
navigation outside Ember's client-side router.
<img width="1397" height="1041" alt="2026-01-01 @ 23 03 34"
src="https://github.com/user-attachments/assets/6a6733f1-999c-44de-ab04-78dd764a1a43"
/>
Ref - https://meta.discourse.org/t/389922
The "Encrypts PM if available" checkbox in automation script options was
a leftover from the discourse-encrypt plugin, which was archived almost
a year ago. Since `EncryptedPostCreator` no longer exists, the option
had no effect - the code always fell back to using the regular
`PostCreator`.
This removes all traces of the `prefers_encrypt` feature:
- The checkbox UI in the PM field component
- The `prefers_encrypt` parameter from `Utils.send_pm`
- References in send_pms and gift_exchange scripts
- The column from the pending_pms table (via post-deploy migration)
- Associated translations and tests
The column removal follows the standard two-phase approach: first
declaring `ignored_columns` in the model, then dropping the column in a
post-deploy migration.
Ref - https://meta.discourse.org/t/-/392664
Allows different types of topic-timer to be automatically configured on
topics. It uses the 'post_created_edited' trigger, which provides all
sorts of filtering options including category/author/etc.
<img width="804" height="270" alt="SCR-20251212-mzrg"
src="https://github.com/user-attachments/assets/90cca3f8-1516-4adf-830a-c2f43125795d"
/>
---------
Co-authored-by: Régis Hanol <regis@hanol.fr>
This commit adds a new "Email on flagged post" automation which can be configured to send emails to users when a post is flagged. The automation can be configured to trigger only when posts are flagged in certain categories or only when certain tags are present on the post's topic.
The body of the email sent can also be configured with the following placeholders available:
* topic_url
* topic_title
* post_url
* post_number
* flagger_username
* flagged_username
* flag_type
* category
* tags
* post_excerpt
Nameless attachments should be removed too,
now `` will be removed from deleted posts, when
using `Remove Upload Markup From Deleted Posts` automation.
Relates to https://github.com/discourse/discourse/pull/36020, we did not
account for posts whose topics have been deleted. In such cases, we need
to provide the Topic context to PostRevisor to avoid errors.
This PR introduces a new automation script that removes uploads attached
to deleted posts.
Using this automation will add a revision to deleted posts by the System
User, removing the upload or attachment references. This Automation goes
a 1000 by every run and does not re-run on already ran posts
When the automation cleans the posts, uploads will no longer have
reference to the post and on the next time the [clean up uploads
job](https://github.com/discourse/discourse/blob/f413e1b0de8c326a12da29f7a4bfa3a6eed004d1/app/jobs/scheduled/clean_up_uploads.rb#L1-L79)
runs it will permanently removes the uploads.
Example of automation run:
```
Hey it is a regular post with a link to [Discourse](https://www.discourse.org)
and an image: 
and a file: [small.pdf|attachment](upload://3bWzVVoRhUXxi7tiPenInoebHyX.pdf) (130 Bytes)
```
When the automation has run in this post, this will be the revisioned
version:
```
Hey it is a regular post with a link to [Discourse](https://www.discourse.org)
and an image:
and a file:
```
---------
Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
I want to use the AI llm_tagger script on
https://discover.discourse.com, but in order to do this the automation
needs to be allowed to run on posts created by the system user.
To accomplish this, I've added the ability for scripts to add a
`allow_system_posts` setting. Existing scripts will still ignore system
posts, but this allows script authors to add a setting to opt-in to
processing them.
We can't enable `Rails/WhereNot` lint/autofix, because it would break
code that uses mini_sql instead of AR (which rubocop, and tbh also we,
can't easily differentiate)
Those are safe because they either:
* are executed in AR model scope definitions
* are clearly chained starting from a AR model
* are less-clearly chained, but still can be traced to a AR model/scope
---------
Co-authored-by: Loïc Guitaut <loic@discourse.org>
This commit replaces the "enabled" checkbox on the automation page with a
toggle at the top of the automation page. We've made this change to make
the automation edit page consistent with the automations list page where
a toggle is used for turning on/off automations, and also consistent
with the rest of admin pages where we've been using toggle for
controlling the enable/disable state.
Internal topic: t/153523.
- For `with_images`, ignore `<img>` tags representing emoji or a quote
avatar
- For `with_links` and `with_uploads`, exclude tags starting with the
letter "a" other than `<a>` (like `<aside>`)
- For `with_code`, the `<pre>` tag wasn't being detected because it was
expecting an extra char before the ">"
Relaxed restrictions:
- Automation posts are not validated for similarity. This was causing
error when PMs were created by regular user with same content and sent
to different users.
- Don't create warning logs when PM target does not exist anymore. When
for example spammer was deleted, delayed PM is not sent, but it is
correct behaviour;
- Allow tags to be applied even if an automation user is not allowed to
tag;
- Restricted category tags should be visible in configuration UI. Still,
they will be applied only when specific topic belongs to correct
category.
1. **Multiselect Support for Choice Fields**
- Added a `multiselect` option to the choices field component
- Updated Field model to accept arrays as values for choices fields
2. **Post Content Feature Filtering**
- Added ability to filter posts based on content features:
- Posts with images
- Posts with links
- Posts with code blocks
- Posts with uploads
3. **Improved Group Filtering**
- Renamed `restricted_user_group` to `restricted_groups` to allow
filtering by multiple groups
- Added `excluded_groups` to replace `ignore_group_members` which was
complex for end users
- Renamed `restricted_groups` to `restricted_inbox_groups` for more
specific PM filtering and clarity.
4. **Public Topics Filter**
- Added a "Public Topics" filter option that excludes all secure
categories
- Allow deciding if we include or exclude sub categories
- Allow filtering to only look at PMs or Topics
- Allow selection of multiple categories
- Migrations to carry all data into new structure
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit allows admins to limit the post created/edited trigger
execution to a specific post user group.
It's for example useful if you want to auto tag all the topics created
by member of a specific group.
* FEATURE: default value to fields in automation
This PR adds the property `extra.default_value` to the fields in automation. This property is used to set the default value of the field.
Reducing the nil checks we have to do in automation fields.
I've added only testing in the `da-choices-field-test.js` file, but we could add tests to all the fields.
* FEATURE: Add trigger_on option to `topic_tags_changed` automation
This new field will allow users to specify when the trigger should be fired. The options are:
- `tags_added_or_removed`: The trigger will be fired when tags are added or removed from the topic.
- `tags_added`: The trigger will be fired when tags are added to the topic.
- `tags_removed`: The trigger will be fired when tags are removed from the topic.
This PR also brings a migration to set the `trigger_on` field to `tags_added_or_removed` for all existing `topic_tags_changed` automations.
* DEV: reorganize the specs in context blocks
* DEV: migration to add trigger_on field default value to topic_tags_changed_trigger
* DEV: `down` migration for `add_trigger_on_field_default_value_to_topic_tags_changed_trigger` migration
* Update plugins/automation/lib/discourse_automation/triggers/topic_tags_changed.rb
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
* Update plugins/automation/config/locales/client.en.yml
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
* DEV: lint files and update topic_tags_changed trigger to use `default_value`
* Revert "FEATURE: default value to fields in automation"
This reverts commit 4d32635c69.
* DEV: remove migration file
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
* FIX: `TopicTagsChanged` trigger not working with multiple tags
The check for when had multiple tags was being exclusive, you had to have all tags to trigger the automation, now it's inclusive, you can trigger the automation if you have any of the tags.
* DEV: add specs for when having multiple categories
* DEV: changed to use unions and intersections for tags
added more tests to check for multiple tags
The script `send_chat_message` when used with the `post_created_edited` trigger now accepts `{{post_quote}}` as placeholder for the value of `message`.
This is made possible by a new method in `utils`. Usage:
```ruby
placeholders["foo"] = utils.build_quote(post)
```
Under certain conditions, a recurring automation can end up in a state with no pending automation records, causing it to not execute again until manually triggered.
We use the `RRule` gem to calculate the next execution date and time for recurring automations. The gem takes the interval, frequency, start date, and a time range, and returns all dates/times within this range that meet the recurrence rule. For example:
```ruby
RRule::Rule
.new("FREQ=DAILY;INTERVAL=1", dtstart: Time.parse("2023-01-01 07:30:00 UTC"))
.between(Time.zone.now, Time.zone.now + 2.days)
# => [Sat, 14 Sep 2024 07:30:00.000000000 UTC +00:00, Sun, 15 Sep 2024 07:30:00.000000000 UTC +00:00]
```
However, if the time component of the first point provided to `.between()` is slightly ahead of the start date (e.g., `dtstart`), the first date/time returned by `RRule` can fall outside the specified range by the same subsecond amount. For instance:
```ruby
RRule::Rule
.new("FREQ=DAILY;INTERVAL=1", dtstart: Time.parse("2023-01-01 07:30:00 UTC"))
.between(Time.parse("2023-01-01 07:30:00.999 UTC"), Time.parse("2023-01-03 07:30:00 UTC"))
.first
# => Sun, 01 Jan 2023 07:30:00.000000000 UTC +00:00
```
Here, the start date/time given to `.between()` is 999 milliseconds after 07:30:00, but the first date returned is exactly 07:30:00 without the 999 milliseconds. This causes the next recurring date to fall into the past if the automation executes within a subsecond of the start time, leading to the automation stalling.
I'm not sure why `RRule` does this, but it seems intentional judging by the source of the `.between()` method:
https://github.com/square/ruby-rrule/blob/b9911b71477ba617184b3769b0c163ba3e8bfb9f/lib/rrule/rule.rb#L28-L32
This commit fixes the issue by selecting the first date ahead of the current time from the list returned by `RRule`, rather than the first date directly.
Internal topic: t/138045.
We're seeing a problem where some recurring automations end up in a state where they don't have any `pending_automations` records scheduled which effectively makes the recurring automation dead. We need to add some debugging to figure out what might be causing this problem.
Internal topic: t/138045.
⚠️ This commit is a revert of a revert due to a migration which was causing `{}` metadata to be transformed into `{"value": [null]}`. The new migration shouldn't cause this and will also clean the existing errors, there shouldn't be any data loss given the affected fields where not containing actual data. We might want to stop storing these empty fields in the future.
To achieve it, this commit does the following:
- create a new `groups field`, ideally we would have reused the existing group field, but many automations now have the expectation that this field will return a group id and not an array of group ids, which makes it a dangerous change
- alter the code in `post_created_edited` to use this new groups field and change the logic to use an array
- migrate the existing group fields post_created_edited automations to change name from `restricted_group` to `restricted_groups`, the component from `group` to `groups` and the metadata from `{"value": integer}` to `{"value": [integer]}`
<!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
* FEATURE: Add user to topic_tags_changed event
Add user to topic_tags_changed event context
Update automation plugin with new arguments in event
Update tests for new arguments
relates to https://github.com/discourse/discourse-chat-integration/pull/214
* DEV: change variable name for better readability
changed `tags` to be payload and used `values_at` to get the values of the keys
To achieve this this commit does the following:
- create a new `groups field, ideally we would have reused the existing group field, but many automations now have the expectation that this field will return a group id and not an array of group ids, which makes it a dangerous change
- alter the code in `post_created_edited` to use this new groups field and change the logic to use an array
- migrate the existing group fields post_created_edited automations to change name from `restricted_group` to `restricted_groups`, the component from `group` to `groups` and the metadata from `{"value": integer}` to `{"value": [integer]}`
Prior to this fix we could exit early if tags was `[]` as `tags && (tags & post.topic.tags.map(&:name)).empty?` would have returned true. This commit ensures it's not the case anymore and adds a test for it.
Co-Authored-By: Martin Brennan <mjrbrennan@gmail.com>
Create a topic can fail in many different ways and we don't want this to impact the rest of the application, the call will now be wrapped in a begin/rescue block and log an error if it fails.
* FEATURE: Change tags sent in topic_tags_changed trigger in discourse_automation
Before, it was sending the old tags and the current tags in topic.
Now, it sends the removed tags and the added tags in the topic.
* DEV: update `missing_tags` to be `removed_tags`
* DEV: add spacing for better readability
Prior to this fix the query in stalled_topic_finder would assume that tags/categories would be nil or an array of ids. However it can be an empty array, in this case the query will not return results.
The automation plugin has 4 custom field types that are array typed. However, array typed custom fields are deprecated and should be migrated to JSON type.
This commit does a couple of things:
1. Migrate all four custom fields to JSON
2. Fix a couple of small bugs that have been discovered while migrating the custom fields to JSON (see the comments on this commit's PR for details https://github.com/discourse/discourse/pull/26939)
Some combinations of start_date and frequency/interval values can cause a recurring automation rule to either trigger before its start_date or never trigger. Example repros:
- Configure a recurring automation with hourly recurrence and a start_date several days ahead. What this will do is make the automation start running hourly immediately even though the start_date is several days ahead.
- Configure a recurring automation with a weekly recurrence and a start_date several weeks ahead. This will result in the automation never triggering even after the start_date.
These 2 scenarios share the same cause which is that the automation plugin doesn't use the start_date as the date for the first run and instead uses the frequency/interval values from the current time to calculate the first run date.
This PR fixes this bug by adding an explicit check for start_date and using it as the first run's date if it's ahead of the current time.
Prior to this fix, any change to an automation would reset `pending_automations`, now we only do it if any value related to recurrence (start_date, interval, frequency, execute_at...) has been changed.
It means that any trigger creating `pending_automations` now needs to manage them in the `on_update` callback.
This commit adds a new option to the `user_updated` trigger of the automation plugin to only trigger an automation for new users that join after the automation is enabled.
Internal topic: t/125829/9.
This commit adds a new automation script for creating topics. It's very similar to the existing 'create a post' automation, except that it posts new topics in a specific category and with optional tags.
Internal topic: t/125829.