Work item list is stuck loading when the Issue work item type has been renamed
This issue was drafted with AI assistance. Please sanity-check the details before starting.
Summary
If a namespace renames its Issue work item type, the work item list gets stuck on a loading spinner whenever you navigate between All items and a saved view. The filters and the item count appear, but the list itself never renders.
Nothing looks wrong from the outside. There is no error message, nothing in the browser console, and every network request succeeds — the list data arrives and is simply never shown.
Reported by a self-managed customer on 19.2.0-ee. Confirmed still present on master.
The trigger is the rename, not saved views. Saved views only expose it because moving between All items and a saved view is the one navigation that remounts the page.
Steps to reproduce
- In a group's work item settings, rename the
Issuework item type to something else, for exampleBug. - Open the work items list (
/work_items). It loads normally. - Click into a saved view (
/work_items/views/:id). - The saved view's filters and total count load, but the list never appears — just a spinner that stays forever.
- Reload the page. Everything loads fine.
- Navigate between two different saved views. These work fine.
- Navigate back to All items. Stuck on a spinner again.
A logged-in user is required. Steps 3 and 7 are the failing ones. Steps 5 and 6 are included because they are the reason this is easy to misread as a saved views bug.
What is the current bug behaviour?
The list area shows a spinner indefinitely. The rest of the page works.
There is a second, quieter problem with the same cause: the user's saved sort preference never loads for that namespace, because the query that reads it never runs. This has no visible symptom, so it will outlive the spinner if only the spinner is fixed.
What is the expected correct behaviour?
The list renders whatever the work item types are named. Renaming a type is a supported feature, so no rename should be able to stop the list loading.
The list should also never be able to sit on a spinner forever. If the preferences query cannot run or does not report back, the list should still render.
Root cause
Two separate faults. The first breaks things; the second is why you can see it.
Fault 1 — the default work item type is looked up by its display name
WorkItemMetadataProvider turns the namespace's types into an object keyed by name (app/assets/javascripts/work_items/components/work_item_metadata_provider.vue:129-133):
const nodes = data?.namespace?.workItemTypes?.nodes || [];
return nodes.reduce((acc, type) => ({ ...acc, [type.name]: type }), {});The planning view then asks for the literal string 'Issue' (app/assets/javascripts/work_items/pages/planning_view.vue:966-971):
const workItemTypeName = this.workItemType || WORK_ITEM_TYPE_NAME_ISSUE; // 'Issue'
return this.getWorkItemTypeConfiguration(workItemTypeName)?.id || '';On the All items list there is no specific type, so it always asks for 'Issue'. Rename that type and there is no Issue key, so workItemTypeId falls back to an empty string for the whole session.
That empty string permanently skips the user preferences query (planning_view.vue:481-483):
skip() {
return !this.workItemTypeId || !this.isLoggedIn;
},Because the query never runs, neither its result() nor its error() handler runs, and neither sets isSortKeyInitialized (planning_view.vue:479 and :485).
Fault 2 — the loading state has no way to recover
isSortKeyInitialized is passed to the list, where it gates the whole list area (app/assets/javascripts/work_items/list/list_view.vue:276-278):
shouldLoad() {
return !this.isInitialLoadComplete || (!this.isSortKeyInitialized && !this.error);
},The second half is enough on its own. This is why the arriving list data does not help, and why there is no error banner: error is undefined, which keeps the condition true.
There is a guard meant to catch exactly this (planning_view.vue:1182-1187), and its comment says so:
workItemTypesConfiguration(workItemTypesConfiguration) {
// When workItemTypesConfiguration becomes available and isSortKeyInitialized is still false,
// set it to true to prevent the loading indicator from showing indefinitelyIt is a plain watcher, so it only runs when the value changes. The types are provided by WorkItemMetadataProvider, which sits above the keyed <router-view> (app/assets/javascripts/work_items/components/app.vue:22-24) and is never torn down:
pageKey() {
return this.$route.params.iid || this.$route.name;
},So:
- Fresh page load — the provider mounts alongside the page, the types genuinely change from empty to populated while the watcher is alive, the guard runs, the list renders. The rename is invisible.
- All items
↔️ saved view — the two routes have different names, sopageKeychanges and the page remounts. The provider does not. The types are already populated on creation, so nothing changes, the guard never runs, and the spinner stays. - Saved view
↔️ saved view — same route name, so no remount, and the flag is already set from earlier. Works.
How this was confirmed
Forcing the preferences query to skip on the saved view route reproduces the spinner exactly: the list data arrives and isInitialLoadComplete becomes true, while shouldLoad stays true and no error is raised. Making the guard run on creation clears it under the same forced conditions.
The customer confirmed they renamed Issue to Bug, and that the type kept the id gid://gitlab/WorkItems::Type/1. That is by design — a converted type reports the id of the system type it came from (ee/app/models/work_items/types_framework/custom/type.rb:110-112):
def to_global_id(_options = {})
converted_from_system_defined_type&.to_global_id ||
::Gitlab::GlobalId.build(self, model_name: 'WorkItems::Type', id: id)
endThe id survives a rename; the name does not. That is the basis for the fix below.
Possible fixes
Both are needed. The first stops the bug, the second stops this class of bug.
1. Stop identifying the default type by its name
planning_view.vue:966-971 should use the stable id. There is already a precedent a few files over (app/assets/javascripts/work_items/components/create_work_item.vue:803-806):
const issueTypeGid = convertToGraphQLId(TYPENAME_WORK_ITEMS_TYPE, 1);
...
(type) => type?.name === WORK_ITEM_TYPE_NAME_ISSUE || type?.id === issueTypeGid,Given the id inheritance above, this is correct rather than a workaround. Hardcoding 1 in the frontend is not lovely though, so it is worth deciding between:
- matching on the id, as
create_work_item.vuealready does — smallest change, works today, no backend work; - exposing a rename-proof field on the GraphQL
WorkItemType(the backend already hasbase_typeon system-defined types, andTypes::WorkItems::TypeTypecurrently exposes onlyid,name,iconNameand behaviour booleans) and matching on that — cleaner, needs a backend change; - not requiring a type at all for the All items list, since a mixed-type list picking "Issue" for its sort preference is arguably wrong regardless of naming.
The third option is worth a look from whoever owns preferences, as it may make the lookup unnecessary here.
2. Make the loading state unable to hang
Run the existing guard on creation as well as on change, so it also covers the case where the value is already resolved when the page mounts (planning_view.vue:1182). Note its current condition requires workItemTypesConfiguration?.length > 0, so a failed or empty types query would still hang — worth deciding whether that should release the loading state too.
Related follow-up work (not in scope here)
- Roughly a dozen other call sites look types up by name via
getWorkItemTypeConfiguration(...), plus several direct comparisons againstWORK_ITEM_TYPE_NAME_ISSUE. These have the same fragility and should be audited once there is an agreed rename-proof identifier. Worth its own issue. - Separately, in
list_view.vuethe list query'serror()handler never setsisInitialLoadComplete(list_view.vue:320-326), and the first half ofshouldLoadis unconditional. So if the first list query fails after a remount, you get a permanent spinner underneath the error banner. Same symptom, different cause, small fix.
Workaround
Renaming the type back to Issue restores normal behaviour. It keeps the same record and the same id, and it passes validation — Custom::Type explicitly allows a converted type to reclaim the name it was converted from (ee/app/models/work_items/types_framework/custom/type.rb:240-241).
Creating a brand new type called Issue also clears the spinner, but it points the lookup at an unrelated type, so preferences get stored against a type no work items use. It also adds a type to the creation menus. Not recommended.
There is no workaround that keeps the rename.