Prevent archived labels from being newly assigned to issuables
What does this MR do and why?
This MR prevents archived labels from becoming new associations at the shared issuable label-assignment boundary.
Archived labels remain available for historical associations, search, and removal. Before this change, Issue REST updates could resolve an archived project or inherited group label through LabelsFinder and persist it as a new association.
This conflicts with the existing archived-label behavior in the UI and quick actions, where archived labels are no longer assignable.
The Issue REST behavior is covered directly, with focused coverage for the shared title- and ID-based assignment paths.
Before
Issue does not have "legacy"
"legacy" is archived
PUT /projects/:id/issues/:issue_iid
add_labels=legacy
=> "legacy" becomes attachedAfter
The update remains successful, but the archived label is not newly attached.
Existing archived associations are not removed merely because the label is archived. Existing replacement, scoped-label, locked-label, and explicit-removal semantics continue to apply normally.
Implementation
Labels::AvailableLabelsService records which IDs are active while performing the existing authorized label resolution:
- ID resolution retrieves
idandarchivedfrom the existing available-label relation. - Title resolution records non-archived IDs from the Label objects it already resolves.
- Newly created labels keep the existing creation behavior and are recorded as active.
Issuable::Callbacks::Labels then distinguishes genuinely new label IDs from IDs already associated with the issuable. Only active resolved IDs can become new associations, while existing IDs remain eligible.
The implementation deliberately does not add archived: false to the title lookup before Labels::FindOrCreateService. Archived title matches must still resolve as existing labels; hiding them could make an existing archived title appear missing and trigger the existing label-creation path.
Global LabelsFinder behavior therefore remains unchanged.
Behavioral coverage
The regression coverage verifies:
- active and archived project-label assignment by title and ID;
- active and archived inherited group labels;
- active labels combined with archived or invalid label titles;
labels=replacement;add_label_ids,label_ids, andremove_label_ids;- preservation of an existing archived association when adding an unrelated active label;
- explicit archived-label removal;
- create-path label candidates;
- the interaction with the work item label limit;
- merged-MR locked-label behavior;
- EE scoped-label behavior;
- no resource-label event for a rejected archived addition;
- existing missing-title creation behavior.
Query performance
The final implementation reuses archive state from the existing authorized label resolution and does not introduce an additional SQL query.
| Path | Issuables | Baseline | Final | Increment |
|---|---|---|---|---|
| IDs | 1 | 52 | 52 | 0 |
| IDs | 20 | 774 | 774 | 0 |
| Titles | 1 | 41 | 41 | 0 |
| Titles | 20 | 782 | 782 | 0 |
The existing ID-resolution query selects one additional boolean column (archived). Focused query specs also verify that filtering the recorded IDs performs no query.
Edge cases and scope
Same-title project and inherited group labels already have order-dependent resolution through the existing title resolver. This MR does not change which matching label that resolver selects. If the resolver selects an archived label, the new-assignment check rejects that selection. Redesigning same-title precedence is outside the scope of this fix.
For scoped labels, a new active label replaces an existing archived label in the same scope, while an active label from another scope preserves the existing archived association. A newly requested archived scoped label is rejected.
For locked labels on merged merge requests, a newly requested archived locked label is rejected, an existing archived locked label remains while an active label is added, and an attempted removal continues to restore the locked label.
Source inspection confirms that Work Item move/clone data sync, project import/export, and GitHub import restore or insert LabelLink records through separate historical-copy paths rather than this interactive assignment filter.
The request regression verifies that a rejected archived addition does not create a resource-label event. Existing system-note generation continues to consume the resulting association changes.
A label could theoretically be archived concurrently after application-level resolution and before persistence. This pre-existing check-then-persist race is outside the scope of this fix.
References
Closes #628860 (closed)
Screenshots or screen recordings
Not applicable. This is a backend-only behavior change with no visible UI changes.
How to set up and validate locally
Run the focused CE/FOSS specs:
bundle exec rspec \
spec/requests/api/issues/put_projects_issues_spec.rb \
spec/services/issuable/callbacks/labels_spec.rb \
spec/services/labels/available_labels_service_spec.rbResult: 100 examples, 0 failures.
Run the same coverage with the EE extension:
FOSS_ONLY=0 bundle exec rspec \
spec/requests/api/issues/put_projects_issues_spec.rb \
spec/services/issuable/callbacks/labels_spec.rb \
spec/services/labels/available_labels_service_spec.rb \
ee/spec/services/ee/issuable/callbacks/labels_spec.rbResult: 103 examples, 0 failures.
Run the focused query regression examples:
bundle exec rspec spec/services/labels/available_labels_service_spec.rb:116Result: 3 examples, 0 failures.
Run RuboCop for all changed files:
bundle exec rubocop \
app/services/issuable/callbacks/labels.rb \
app/services/labels/available_labels_service.rb \
spec/requests/api/issues/put_projects_issues_spec.rb \
spec/services/labels/available_labels_service_spec.rb \
spec/services/issuable/callbacks/labels_spec.rb \
ee/spec/services/ee/issuable/callbacks/labels_spec.rbResult: 6 files inspected, no offenses detected.
The reviewer-requested active-plus-archived, active-plus-invalid-title, and label-limit interaction examples were also run against the pre-fix implementation and failed for the expected reasons.
MR acceptance checklist
- The regression coverage fails against the pre-fix implementation and passes with this change.
- CE and EE behavior, REST behavior, authorization boundaries, and query counts were reviewed.
- Existing documentation already describes archived labels as unavailable for new selection while preserving existing associations.
- No migration, schema change, new query, feature flag, or UI change is introduced.
- The final diff, commit message, changelog metadata, and repository-local style checks were reviewed.
AI assistance
AI tooling was used to assist with investigation and drafting. The contributor independently reviewed the implementation, reproduced the bug locally, verified the regression test and fix, and takes responsibility for the submitted changes.