| id | rules/actions | ||||
|---|---|---|---|---|---|
| title | Rule actions | ||||
| category | rules | ||||
| tags |
|
||||
| summary | What a rule does when it matches — full vocabulary including the quarantine and delete actions. | ||||
| help_context |
|
||||
| related |
|
When a rule matches, every entry in its actions array runs. Actions
are applied in order but each is independent — a failure in one
does not abort the others. Across many rules matching the same file,
results are aggregated: tags accumulate (deduped); severity escalates
to the highest matched value; a delete action by any matched rule
removes the file (delete is one-way — once any rule deletes, the row
and file are gone). The quarantine aggregation is gone
along with the action itself.
The full vocabulary is published by the backend at
GET /api/v1/rules/vocabulary and rendered by the visual rule
builder — you don't have to memorize it.
Set the file's severity to a fixed level. Severity escalation across
rules is monotonic: if one rule sets warn and another sets high,
the file ends up at high.
{ "type": "set_severity", "severity": "warn" }Valid values: ok, info, warn, high, error, crit.
Add a tag to the file in Auditarr's index. Tags are de-duped across rules so the same tag from two rules doesn't create two rows.
{ "type": "add_tag", "tag": "video" }Rule-applied tags persist with source="rule" so disabling the
rule cleanly removes only the tags it added (other sources —
Sonarr / Radarr / Bazarr / operator-applied — are untouched).
Canonical tag vocabulary. The shipped rule catalog uses a small set of broad buckets so the Files-page chip strip groups related rule matches under the same filter chip rather than producing one chip per rule. Operator-authored rules should reuse these buckets when possible:
| Tag | Covers |
|---|---|
video |
Video codec / profile / level issues, unknown video codec |
audio |
Audio codec issues, lossless tracks that won't direct-play |
container |
Container-format issues (AVI, WMV, MPEG-TS, codec-in-wrong-container) |
subtitle |
Subtitle codec issues, missing subtitle tracks |
resolution |
Resolution-related concerns (e.g. unusual 4K combinations) |
bitrate |
Very high / very low bitrate concerns |
frame-rate |
Frame-rate concerns (VFR, unusual rates) |
hdr |
HDR10 / HDR10+ / HLG concerns |
dolby-vision |
Dolby Vision profile concerns (4, 5, 6, 8.1, …) |
integrity |
Data-integrity concerns: orphaned files, suspicious size, container/stream duration mismatch, missing seek index, MP4 moov-at-end, negative PTS/DTS, zero-packet streams, cover-art-as-video, extra video tracks |
security |
Security concerns: executables in library, VirusTotal hits |
metadata |
Metadata oddities: split-part files, embedded-title issues |
edge-case |
Rare conditions that don't fit the above buckets |
Queue an optimization job for the file using the named profile.
{ "type": "queue_optimization", "profile": "h265-medium" }The profile must exist (configured under Optimization → Profiles).
Send a notification through the configured providers, filtered by the active channel.
{
"type": "notify",
"channel": "discord-ops",
"message": "Codec mismatch: {path}"
}The channel is required; message is optional (defaults to a
generic format string derived from the rule name).
Enqueue the matched file for a VirusTotal hash lookup. The action
takes no parameters — the file's SHA-256 is computed (or read from
cache) and looked up against the configured VirusTotal API key.
The result lands on the file's vt_status column (clean /
suspicious / malicious / not_found / error).
{ "type": "vt_lookup" }Multiple rules firing vt_lookup against the same file produce one
queue row (the vt_queue table has a unique constraint on
media_file_id). Requires the VirusTotal plugin to be enabled; the
action is a no-op on installs without it. See the VirusTotal
integration setup in docs/integrations/.
Trigger an upstream search on a configured Sonarr / Radarr / Bazarr integration for the matched file. Useful for "this file is orphaned → re-find it" or "this file is missing English subs → poke Bazarr."
{
"type": "search_upstream",
"target": "sonarr",
"integration_id": "<integration-row-id>"
}target is one of sonarr / radarr / bazarr;
integration_id selects which integration of that kind to call
(operators can have multiple Sonarr instances). The full
behaviour, dedup rules, and audit-log shape are documented in
docs/rules/search-upstream.md.
The quarantine action no longer exists. It was retired
along with the rest of the quarantine workflow — "delete means
delete."
If your rule used to quarantine, you have two paths now:
- Tag the matched files (
add_tag) and let the operator review the tagged set. - If you want the file gone, use a
deleteaction with a descriptivereason. The audit log records every removal.
Stored type: "quarantine" actions from older databases are
rewritten to type: "delete" on upgrade (the reason is
preserved), so existing rules don't break — but the behaviour
change is significant: those rules now hard-delete instead of
flagging. Review your rule set after upgrading from any release
that still had the quarantine action.
Move the file to the trash directory and remove its index row.
Unconditional — there is no confirm flag (the soft-delete
path went away with the quarantine workflow).
The rule body that contains any delete action must also
set "acknowledged_destructive": true at the rule level (not on
the action). Without that flag the engine rejects the rule. The
flag is forbidden on rules that don't contain a delete action.
{ "type": "delete", "reason": "Plex incompatible codec" }reason is optional but strongly recommended — it lands
verbatim in the file.deleted audit-log entry that the rule
service writes for every successful delete. Operators reading
the audit trail see WHY a file was removed, not just WHEN.
When a delete action matches:
- The file is moved (
shutil.move) todata_dir/trash/{id}__{name}. The numeric id prefix prevents same-name collisions across libraries. - An audit-log entry is written (
action: "file.deleted",actor_label: "rules",metadata: { path, reason, trash_path }). - The
MediaFilerow is removed from the database. media.deletedis emitted on the event bus with the reason.
Filesystem failures are non-fatal. If the move fails
(permission, disk full, target conflict), the failure is logged
at rules.hard_delete.failed and the row is preserved. You
never lose both the file and its index entry in the same
operation.
Audit-log failures are non-fatal at the file level — if the
audit write fails for some reason, the file has already moved to
trash and the row gets removed regardless. The audit failure is
logged loudly at rules.hard_delete.audit_failed so an operator
notices the gap.
The trash directory accumulates files; emptying it is the operator's responsibility — will surface a UI affordance.
The visual builder at /rules/{id}/edit renders every action type
the backend vocabulary publishes, so any new action added in a
future stage shows up automatically without any frontend change.
For the delete action specifically, the builder renders the
optional reason as a labeled text input. retired the
old confirm checkbox — the hard-delete semantics no longer
have a gating flag to expose.
The Action schema is a discriminated Pydantic union — new action
types are added by appending a model with a unique type literal and
threading it through the evaluator's _apply_action dispatch and the
rules service's persist step. See app/rules/schema.py and
app/rules/evaluator.py for the existing pattern.