Problem
Group awareness is unevenly distributed across automation actions:
- Delete has full group semantics — member expansion via
MembersForHash, once-per-group dedupe (processedGroupKeys), and verify_overlap ambiguity safety (internal/services/automations/service.go).
- Category has
GroupID and IncludeCrossSeeds (models/automation.go CategoryAction).
- Tags have neither:
TagAction (models/automation.go:943) carries no GroupID, and tag changes are computed strictly per-hash (tagChanges[hash], service.go tag block).
Consequence for cross-seed libraries: any tag rule whose condition qualifies a release ends up tagging every copy of that release — e.g. a rule meant to mark releases for review queues N identical entries, one per tracker copy. There's no way to say "tag this release once" or "keep tags consistent across a release's copies."
Proposal
Extend TagAction with the same group vocabulary the other actions use:
type TagAction struct {
...
GroupID string `json:"groupId,omitempty"` // resolve members like Category/Delete
GroupMode string `json:"groupMode,omitempty"` // "", "canonical", "members"
}
canonical — apply the tag change to exactly one member per group per run. Deterministic pick: first hash in the group's sorted member list (the ordering buildGroupIndex already guarantees), with a once-per-group guard mirroring delete's processedGroupKeys. Use case: queue/marker tags that must exist once per release regardless of copy count.
members — expand the tag change to all group members when the trigger torrent matches. Use case: propagating state tags (e.g. archived) so every current copy of a release reflects it, keeping group state visible on each torrent in the UI.
- Empty/absent
GroupMode (or GroupID) keeps today's per-torrent behavior — fully backward compatible.
Safety
- Reuse the ambiguity handling from group-aware delete: for contentPath-keyed groups with
ContentPath == SavePath, honor the group definition's AmbiguousPolicy (verify_overlap with MinFileOverlapPercent, or skip) before expanding/deduping, so flat layouts can't smear tags across unrelated torrents.
DeleteFromClient/managed-tag reset semantics (collectManagedTagsForClientReset) should treat group-applied tags identically to per-torrent ones.
Notes
Complements GROUP_HAS_TAG (#2255): that issue makes group tag state readable in conditions; this one makes it writable by actions. Together they let tag state converge at the release-group level instead of per copy, but each stands alone.
Problem
Group awareness is unevenly distributed across automation actions:
MembersForHash, once-per-group dedupe (processedGroupKeys), andverify_overlapambiguity safety (internal/services/automations/service.go).GroupIDandIncludeCrossSeeds(models/automation.goCategoryAction).TagAction(models/automation.go:943) carries noGroupID, and tag changes are computed strictly per-hash (tagChanges[hash],service.gotag block).Consequence for cross-seed libraries: any tag rule whose condition qualifies a release ends up tagging every copy of that release — e.g. a rule meant to mark releases for review queues N identical entries, one per tracker copy. There's no way to say "tag this release once" or "keep tags consistent across a release's copies."
Proposal
Extend
TagActionwith the same group vocabulary the other actions use:canonical— apply the tag change to exactly one member per group per run. Deterministic pick: first hash in the group's sorted member list (the orderingbuildGroupIndexalready guarantees), with a once-per-group guard mirroring delete'sprocessedGroupKeys. Use case: queue/marker tags that must exist once per release regardless of copy count.members— expand the tag change to all group members when the trigger torrent matches. Use case: propagating state tags (e.g.archived) so every current copy of a release reflects it, keeping group state visible on each torrent in the UI.GroupMode(orGroupID) keeps today's per-torrent behavior — fully backward compatible.Safety
ContentPath == SavePath, honor the group definition'sAmbiguousPolicy(verify_overlapwithMinFileOverlapPercent, orskip) before expanding/deduping, so flat layouts can't smear tags across unrelated torrents.DeleteFromClient/managed-tag reset semantics (collectManagedTagsForClientReset) should treat group-applied tags identically to per-torrent ones.Notes
Complements
GROUP_HAS_TAG(#2255): that issue makes group tag state readable in conditions; this one makes it writable by actions. Together they let tag state converge at the release-group level instead of per copy, but each stands alone.