# メタタグ整理：未使用タグ削除、スペルミスタグ削除、アイコン追加、正しいタググループへ移動？

**URL:** https://meta.discourse.org/t/meta-tag-cleanup-delete-unused-ones-delete-misspelled-ones-add-icons-and-move-to-correct-tag-groups/364105
**Category:** Site feedback
**Tags:** tags
**Created:** [2025 年 4 月 30 日午前 7:09 UTC](https://meta.discourse.org/t/meta-tag-cleanup-delete-unused-ones-delete-misspelled-ones-add-icons-and-move-to-correct-tag-groups/364105 "2025-04-30T07:09:14Z")
**Posts on this page:** 1
**Showing post:** 18

<div class="post-metadata">

### Author: ![zogstrip](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/zogstrip/32/512781_2.png) [@zogstrip](https://meta.discourse.org/u/zogstrip)
#### Post date: [2026 年 4 月 23 日午前 9:28 UTC](https://meta.discourse.org/t/meta-tag-cleanup-delete-unused-ones-delete-misspelled-ones-add-icons-and-move-to-correct-tag-groups/364105/18 "2026-04-23T09:28:24Z")

</div>

すべて拾えたと思います😅

> <https://github.com/discourse/discourse/pull/39399>
>
> Tag search (\`/tags/filter/search\`) was not fully consistent with category-based …tag visibility. A tag restricted to a private category via \`CategoryTag\` or \`CategoryTagGroup\` could still appear in several of the endpoint's outputs for users who did not have access to that category: as a disabled entry with an explanation in the composer autocomplete, as a regular allowed result when the tag filter dropdown on topic lists queried without \`filterForInput\`, or as a \`forbidden\_message\` when the exact tag name was typed. The missing-parent-tag reason could include a parent tag name even when the parent itself was restricted to a category the viewer could not access, and the synonym-exclusion reason could include the synonym's target name without checking whether the target was itself visible. The serialized tag payload also included the \`target\_tag: { id, name, slug }\` field for every synonym regardless of target visibility, and \`fetch\_category\` resolved any \`categoryId\` the caller passed without checking visibility, which made the behaviour for an invisible existing category distinguishable from the behaviour for a non-existent one. Finally, the fallback disabled-tag reason always said "cannot be used in this category" even when the request had no category context at all.
> 
> The underlying cause is that "visibility" in \`DiscourseTagging\` was defined purely in terms of \`TagGroupPermission\` and ignored category access entirely, so each code path in \`Tags::Search\` was relying on its own partial checks (or none). Most of the behaviour is long-standing; the disabled-entries path in autocomplete is a regression from #39072, but the others have been in place for years.
> 
> The fix redefines visibility to include category access and routes every tag-search code path through that single definition.
> 
> \`DiscourseTagging.visible\_tags\` now composes a new \`filter\_visible\_in\_accessible\_categories\` helper on top of the existing tag-group-permission check: a tag is kept only if it has no category restriction, or if at least one of its restrictions points to a category in \`guardian.allowed\_category\_ids\`. The helper is a single \`WHERE\` clause with one named bind reused twice.
> 
> \`DiscourseTagging.filter\_allowed\_tags\` enforces this visibility in-SQL by adding \`t.id IN (visible\_tags subquery)\` to its builder for non-admin callers. That single subquery subsumes the narrower hidden-tag-group \`EXCEPT\` block the method used to build, so the whole filter is now one trip to the database instead of being post-filtered in Ruby.
> 
> \`Tags::Search\` steps all flow through the new visibility universe. \`search\_tags\` inherits the guarantee through \`filter\_allowed\_tags\`. \`append\_disabled\_tags\` and \`detect\_forbidden\_tag\` both start from \`DiscourseTagging.filter\_visible(Tag.all, guardian)\` via a private \`visible\_tags\` helper, so neither can surface a tag outside the user's scope. \`fetch\_category\` intersects the requested id with \`guardian.allowed\_category\_ids\` and returns \`nil\` otherwise, so categories the caller cannot see are treated the same as categories that do not exist.
> 
> \`explain\_exclusion\` gates its two name-surfacing branches on visibility: the synonym-exclusion reason is only emitted when the target tag is visible, and the missing-parent-tag reason is only emitted when the parent tag is visible. When neither of those reasons (nor any category-based reason) applies, the fallback now picks between \`tags.forbidden.in\_this\_category\` (when \`params.categoryId\` is present) and a new \`tags.forbidden.not\_allowed\` wording (when it is not), so the message matches the actual context of the request.
> 
> \`TagsController.tag\_counts\_json\` filters the \`target\_tags\` lookup through \`DiscourseTagging.filter\_visible\`, so synonyms whose targets are invisible no longer carry a \`target\_tag\` field in the payload. This benefits every caller of \`tag\_counts\_json\` (search, tag index, tag show, the hashtag data source, and the detailed tag serializer), not just the tag search endpoint.
> 
> The accompanying specs cover each path both positively and negatively: admins and authorized users still see their tags, partial matches still return disabled entries with the right reason, exact matches still produce \`forbidden\_message\` for tags the user can see; unauthorized users, anonymous users, invisible-category probes, synonym payloads, and parent-tag reasons are all verified not to surface anything outside the user's scope.
> 
> Ref - t/364105

---

_[View the full topic](https://meta.discourse.org/t/meta-tag-cleanup-delete-unused-ones-delete-misspelled-ones-add-icons-and-move-to-correct-tag-groups/364105)._
