Can't delete unit from Library v2

Hi,

I tried to copy a unit from a course to a library v2. That did not work (there was an error). However, it seems to have still created units, which are not published and non-deletable.

When I try to delete those two units on the right, it does not give an error message, it just does not work.

In the CMS logs, I get:

cms-1  | 2026-03-06 15:16:08,742 ERROR 39 [django.request] [user 5] [ip 208.118.221.4] log.py:253 - Internal Server Error: /api/libraries/v2/blocks/lb:CQ:Navigation:html:naviguer-dans-la-formation-c4e9db/hierarchy/
cms-1  | Traceback (most recent call last):
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
cms-1  |     response = get_response(request)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
cms-1  |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
cms-1  |     return view_func(request, *args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/views/generic/base.py", line 105, in view
cms-1  |     return self.dispatch(request, *args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/utils/decorators.py", line 48, in _wrapper
cms-1  |     return bound_method(*args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 515, in dispatch
cms-1  |     response = self.handle_exception(exc)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 475, in handle_exception
cms-1  |     self.raise_uncaught_exception(exc)
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
cms-1  |     raise exc
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 512, in dispatch
cms-1  |     response = handler(request, *args, **kwargs)
cms-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/utils.py", line 24, in wrapped_fn
cms-1  |     return fn(*args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/blocks.py", line 377, in get
cms-1  |     hierarchy = api.get_library_object_hierarchy(key)
cms-1  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/containers.py", line 641, in get_library_object_hierarchy
cms-1  |     return ContainerHierarchy.create_from_library_object_key(object_key)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/container_metadata.py", line 252, in create_from_library_object_key
cms-1  |     root_items = [get_component_from_usage_key(object_key)]
cms-1  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/edx-platform/openedx/core/djangoapps/xblock/api.py", line 208, in get_component_from_usage_key
cms-1  |     return authoring_api.get_component_by_key(
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/components/api.py", line 345, in get_component_by_key
cms-1  |     .get(
cms-1  |      ^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
cms-1  |     return getattr(self.get_queryset(), name)(*args, **kwargs)
cms-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1  |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/query.py", line 635, in get
cms-1  |     raise self.model.DoesNotExist(
cms-1  | openedx_learning.apps.authoring.components.models.Component.DoesNotExist: Component matching query does not exist.

Is there any way to ā€œunbreakā€ this state ?

Note: I actually see similar errors in the logs when just viewing the library’s page.

Thank you for reporting this. I’ve filed the following to track this issue:

I’m guessing there are at least three things wrong:

  1. We’re not properly wrapping the paste operation in a transaction, so part of it is completing and leaving the data structure in a corrupted state.
  2. There some specific error case that’s not being covered.
  3. The item deletion code isn’t robust enough.

I believe the error message you’ve pasted above is it trying to read information about the broken Unit in the library. Was there an error message that happened before that, when the paste operation occurred? That stacktrace would probably have import_staged_content_from_user_clipboard in it somewhere.

Thank you.

I reproduced the error in our dev environment.

This is the error:

cms-1                   | 2026-03-10 12:46:11,832 INFO 22 [celery.app.trace] [user 5] [ip 172.97.53.68] trace.py:128 - Task openedx.core.djangoapps.content.search.tasks.upsert_library_block_index_doc[ec213237-2ebf-411b-a2b2-5270fd78a357] succeeded in 0.5759167210198939s: None
cms-1                   | 2026-03-10 12:46:11,833 INFO 22 [openedx_events.tooling] [user 5] [ip 172.97.53.68] tooling.py:179 - Responses of the Open edX Event <org.openedx.content_authoring.library_block.updated.v1>:
cms-1                   | [(<function auto_tag_library_block at 0x7f8334fc3600>, None),
cms-1                   |  (<function library_block_updated_handler at 0x7f8334dea340>, None)]
cms-1                   | 2026-03-10 12:46:11,864 ERROR 22 [root] [user None] [ip None] signals.py:22 - Uncaught exception from None
cms-1                   | Traceback (most recent call last):
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/api.py", line 155, in get_or_create_file_content
cms-1                   |     content = Content.objects.get(
cms-1                   |               ^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
cms-1                   |     return getattr(self.get_queryset(), name)(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/query.py", line 635, in get
cms-1                   |     raise self.model.DoesNotExist(
cms-1                   | openedx_learning.apps.authoring.contents.models.Content.DoesNotExist: Content matching query does not exist.
cms-1                   |
cms-1                   | During handling of the above exception, another exception occurred:
cms-1                   |
cms-1                   | Traceback (most recent call last):
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
cms-1                   |     response = get_response(request)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
cms-1                   |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
cms-1                   |     return view_func(request, *args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/views/generic/base.py", line 105, in view
cms-1                   |     return self.dispatch(request, *args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/utils/decorators.py", line 48, in _wrapper
cms-1                   |     return bound_method(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 515, in dispatch
cms-1                   |     response = self.handle_exception(exc)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 475, in handle_exception
cms-1                   |     self.raise_uncaught_exception(exc)
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
cms-1                   |     raise exc
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 512, in dispatch
cms-1                   |     response = handler(request, *args, **kwargs)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/utils.py", line 24, in wrapped_fn
cms-1                   |     return fn(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/libraries.py", line 523, in post
cms-1                   |     result = api.import_staged_content_from_user_clipboard(library_key, request.user)
cms-1                   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 659, in import_staged_content_from_user_clipboard
cms-1                   |     return _import_staged_block_as_container(
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 608, in _import_staged_block_as_container
cms-1                   |     child_metadata = _import_staged_block(
cms-1                   |                      ^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 479, in _import_staged_block
cms-1                   |     content = authoring_api.get_or_create_file_content(
cms-1                   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/api.py", line 173, in get_or_create_file_content
cms-1                   |     content.write_file(ContentFile(data))
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/models.py", line 366, in write_file
cms-1                   |     storage.save(self.path, file)
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/base.py", line 49, in save
cms-1                   |     name = self._save(name, content)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/filesystem.py", line 100, in _save
cms-1                   |     os.makedirs(directory, exist_ok=True)
cms-1                   |   File "<frozen os>", line 215, in makedirs
cms-1                   |   File "<frozen os>", line 215, in makedirs
cms-1                   |   File "<frozen os>", line 225, in makedirs
cms-1                   | PermissionError: [Errno 13] Permission denied: '/openedx/media-private/openedx-learning'
cms-1                   | 2026-03-10 12:46:11,895 ERROR 22 [django.request] [user 5] [ip 172.97.53.68] log.py:253 - Internal Server Error: /api/libraries/v2/lib:CQ:bibliothequetest/paste_clipboard/
cms-1                   | Traceback (most recent call last):
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/api.py", line 155, in get_or_create_file_content
cms-1                   |     content = Content.objects.get(
cms-1                   |               ^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/manager.py", line 87, in manager_method
cms-1                   |     return getattr(self.get_queryset(), name)(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/db/models/query.py", line 635, in get
cms-1                   |     raise self.model.DoesNotExist(
cms-1                   | openedx_learning.apps.authoring.contents.models.Content.DoesNotExist: Content matching query does not exist.
cms-1                   |
cms-1                   | During handling of the above exception, another exception occurred:
cms-1                   |
cms-1                   | Traceback (most recent call last):
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
cms-1                   |     response = get_response(request)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
cms-1                   |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
cms-1                   |     return view_func(request, *args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/views/generic/base.py", line 105, in view
cms-1                   |     return self.dispatch(request, *args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/utils/decorators.py", line 48, in _wrapper
cms-1                   |     return bound_method(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 515, in dispatch
cms-1                   |     response = self.handle_exception(exc)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 475, in handle_exception
cms-1                   |     self.raise_uncaught_exception(exc)
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
cms-1                   |     raise exc
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/rest_framework/views.py", line 512, in dispatch
cms-1                   |     response = handler(request, *args, **kwargs)
cms-1                   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/utils.py", line 24, in wrapped_fn
cms-1                   |     return fn(*args, **kwargs)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/rest_api/libraries.py", line 523, in post
cms-1                   |     result = api.import_staged_content_from_user_clipboard(library_key, request.user)
cms-1                   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 659, in import_staged_content_from_user_clipboard
cms-1                   |     return _import_staged_block_as_container(
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 608, in _import_staged_block_as_container
cms-1                   |     child_metadata = _import_staged_block(
cms-1                   |                      ^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/edx-platform/openedx/core/djangoapps/content_libraries/api/blocks.py", line 479, in _import_staged_block
cms-1                   |     content = authoring_api.get_or_create_file_content(
cms-1                   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/api.py", line 173, in get_or_create_file_content
cms-1                   |     content.write_file(ContentFile(data))
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/openedx_learning/apps/authoring/contents/models.py", line 366, in write_file
cms-1                   |     storage.save(self.path, file)
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/base.py", line 49, in save
cms-1                   |     name = self._save(name, content)
cms-1                   |            ^^^^^^^^^^^^^^^^^^^^^^^^^
cms-1                   |   File "/openedx/venv/lib/python3.11/site-packages/django/core/files/storage/filesystem.py", line 100, in _save
cms-1                   |     os.makedirs(directory, exist_ok=True)
cms-1                   |   File "<frozen os>", line 215, in makedirs
cms-1                   |   File "<frozen os>", line 215, in makedirs
cms-1                   |   File "<frozen os>", line 225, in makedirs
cms-1                   | PermissionError: [Errno 13] Permission denied: '/openedx/media-private/openedx-learning'
cms-1                   | [pid: 22|app: 0|req: 351/943] 172.18.0.2 () {70 vars in 4391 bytes} [Tue Mar 10 12:46:10 2026] POST /api/libraries/v2/lib:CQ:bibliothequetest/paste_clipboard/ => generated 8704 bytes in 1389 msecs (HTTP/1.1 500) 10 headers in 649 bytes (1 switches on core 0)

I think this initial error is the same as issue Can't upload files to libraries v2 - #3 by mboisson

How can I go and clean this up ? In fact, I would like to delete the library and its corrupted units altogether.

Thank you for the stack trace.

That one should be straightforward. Go to the Content Libraries in your Studio’s Django Admin: /admin/content_libraries/contentlibrary/. You can select the library and use the ā€œdeleteā€ action from the admin dropdown to remove it.

The error is a desynchronization between the database and the search index when the permissions error occurs. I created a fix in openedx-platform#38161