Checklist
TagStudio Version
main branch commit 6aa0cf7
Operating System & Version
NixOS 26.05
Description
My initial discovery of the issue was that I went to use the "fix unlinked" tool and after pressing "Refresh" and then "Search & Relink" I can see in the logs that it's failing the inserts with "Database is locked" error.
After shutting it down and restarting and the refreshing again I closed the tool and now I can't add any tags because of "database is locked" errors.
I then rolled back to before #1432 as it seemed like the latest change, and now it works. I believe that the issue might stem from the new implementation of all_entries in src/tagstudio/core/library/alchemy/library.py where I believe it's loosing track of a session that can't be flushed/closed.
This I believe happens in:
def all_entries(self, with_joins: bool = False) -> Iterator[Entry]:
"""Load entries without joins."""
with Session(self.engine) as session:
return self.__all_entries(session, with_joins)
where the inner function:
def __all_entries(self, session: Session, with_joins: bool = False) -> Iterator[Entry]:
"""Load entries without joins."""
stmt = select(Entry)
if with_joins:
# load Entry with all joins and all tags
stmt = (
stmt.outerjoin(Entry.text_fields)
.outerjoin(Entry.datetime_fields)
.outerjoin(Entry.tags)
)
stmt = stmt.options(
contains_eager(Entry.text_fields),
contains_eager(Entry.datetime_fields),
contains_eager(Entry.tags),
)
stmt = stmt.distinct()
entries = session.execute(stmt).scalars()
if with_joins:
entries = entries.unique()
for entry in entries:
yield entry
session.expunge(entry)
is a generator that holds onto the other session.
This generator is then used in the unlinking refresh to find all the entries, but the session is never closed because it has left the original scope. This then puts the database in a deadlock, and can only be freed by killing the application.
I also think this verifies the suspicion that the test in tests/macros/test_missing_files.py doesn't work.
Expected Behavior
I should be able to refresh and relink entries.
Steps to Reproduce
- Open "Tools" -> "Fix Unlinked Entries"
- Click "Refresh"
- Close the pop-up
- Add tag to existing entry
Logs
2026-07-14 01:35:27 [info ] [Library] Library DB version: 202
2026-07-14 01:35:27 [info ] [Library] Creating DB tables...
2026-07-14 01:35:27 [info ] [Library] Library migrated to DB version 202
2026-07-14 01:35:27 [info ] [Config] Thumbnail Cache Size: 1.02 GB
2026-07-14 01:35:27 [info ] [Ignore] Processing the .ts_ignore file... last_mtime=None library=PosixPath('/TagStudio/test-library') new_mtime=1765618780.0270758
2026-07-14 01:35:27 [info ] [Ignore] glob_patterns=[]
2026-07-14 01:35:27 [info ] [Ignore] No updates to the .ts_ignore detected last_mtime=1765618780.0270758 library=PosixPath('/TagStudio/test-library') new_mtime=1765618780.0270758
2026-07-14 01:35:27 [info ] searching library filter=BrowsingState(page_index=0, page_positions={}, sorting_mode=<SortingModeEnum.DATE_ADDED: 'file.date_added'>, ascending=False, random_seed=0, show_hidden_entries=False, query=None) query_full='SELECT DISTINCT entries.id \nFROM entries \nWHERE (entries.id NOT IN (SELECT tag_entries.entry_id \nFROM tag_entries \nWHERE tag_entries.tag_id IN (SELECT tags.id \nFROM tags \nWHERE tags.is_hidden))) ORDER BY entries.id DESC'
2026-07-14 01:35:27 [info ] SQL Execution finished (0 seconds)
2026-07-14 01:35:27 [info ] items to render count=8
2026-07-14 01:35:32 [info ] Closing Library...
2026-07-14 01:35:32 [info ] [Library] Creating SQLAlchemy Engine connection_string=sqlite:////.TagStudio/ts_library.sqlite poolclass=<class 'sqlalchemy.pool.impl.NullPool'>
2026-07-14 01:35:32 [info ] [Library] Opening SQLite Library library_dir=PosixPath('')
2026-07-14 01:35:32 [info ] [Library] Library DB version: 202
2026-07-14 01:35:32 [info ] [Library] Creating DB tables...
2026-07-14 01:35:32 [info ] [Library] Library migrated to DB version 202
2026-07-14 01:35:33 [info ] [Config] Thumbnail Cache Size: 1.02 GB
2026-07-14 01:35:33 [info ] [Ignore] Processing the .ts_ignore file... last_mtime=1765618780.0270758 library=PosixPath('') new_mtime=1763158157.844917
2026-07-14 01:35:33 [info ] [Ignore] glob_patterns=[]
2026-07-14 01:35:33 [info ] [Ignore] No updates to the .ts_ignore detected last_mtime=1763158157.844917 library=PosixPath('') new_mtime=1763158157.844917
2026-07-14 01:35:33 [info ] searching library filter=BrowsingState(page_index=0, page_positions={}, sorting_mode=<SortingModeEnum.DATE_ADDED: 'file.date_added'>, ascending=False, random_seed=0, show_hidden_entries=False, query=None) query_full='SELECT DISTINCT entries.id \nFROM entries \nWHERE (entries.id NOT IN (SELECT tag_entries.entry_id \nFROM tag_entries \nWHERE tag_entries.tag_id IN (SELECT tags.id \nFROM tags \nWHERE tags.is_hidden))) ORDER BY entries.id DESC'
2026-07-14 01:35:33 [info ] SQL Execution finished (0.32 seconds)
2026-07-14 01:35:33 [info ] items to render count=193275
2026-07-14 01:35:38 [info ] [UnlinkedRegistry] Refreshing unlinked files...
2026-07-14 01:35:58 [info ] [QtDriver] Selecting Items: append=False bridge=False item_id=253255
2026-07-14 01:35:58 [warning ] [FieldContainers] Updating Selection entry_id=253255
2026-07-14 01:35:58 [info ] [Library] Time it took to get entry: 0.01 seconds with_fields=True with_tags=True
2026-07-14 01:35:58 [info ] [emit_badge_signals] Emitting emit_on_absent=True tag_ids=set()
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.ARCHIVED: 'Archived'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.FAVORITE: 'Favorite'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver] Selecting Items: append=False bridge=False item_id=253255
2026-07-14 01:35:58 [warning ] [FieldContainers] Updating Selection entry_id=253255
2026-07-14 01:35:58 [info ] [Library] Time it took to get entry: 0 seconds with_fields=True with_tags=True
2026-07-14 01:35:58 [info ] [emit_badge_signals] Emitting emit_on_absent=True tag_ids=set()
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.ARCHIVED: 'Archived'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.FAVORITE: 'Favorite'>: False} origin_id=0
2026-07-14 01:35:59 [info ] [SearchPanel] Updating items limit=25
2026-07-14 01:36:01 [info ] searching tags limit=25 results=2 search=photo statement="SELECT tag_aliases.tag_id, tag_aliases.name \nFROM tag_aliases \nWHERE (lower(tag_aliases.name) LIKE '%' || lower(:name_1) || '%')"
2026-07-14 01:36:01 [info ] [SearchPanel] Search results results=[<Tag ID: 1200 Name: Photo>, <Tag ID: 2637 Name: AI Generated Photo>, <Tag ID: 1207 Name: Avatar>, <Tag ID: 1201 Name: Banner>, <Tag ID: 1243 Name: Selfie>]
2026-07-14 01:36:03 [info ] [FieldContainers][add_tags_to_selected] selected=[253255] tag_ids=[1200]
2026-07-14 01:36:03 [info ] [Library][add_tags_to_entries] entry_ids=[253255] tag_ids=[1200]
Traceback (most recent call last):
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1147, in _commit_impl
self.engine.dialect.do_commit(self.connection)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/default.py", line 715, in do_commit
dbapi_connection.commit()
~~~~~~~~~~~~~~~~~~~~~~~^^
sqlite3.OperationalError: database is locked
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/TagStudio/src/tagstudio/qt/controllers/preview_panel_controller.py", line 56, in _add_tag_to_selected
self._containers.add_tags_to_selected(tag_id)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/TagStudio/src/tagstudio/qt/mixed/field_containers.py", line 252, in add_tags_to_selected
self.driver.add_tags_to_selected_callback(tag_ids)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/TagStudio/src/tagstudio/qt/ts_qt.py", line 912, in add_tags_to_selected_callback
self.lib.add_tags_to_entries(selected, tag_ids)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/TagStudio/src/tagstudio/core/library/alchemy/library.py", line 1769, in add_tags_to_entries
session.commit()
~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/session.py", line 2035, in commit
trans.commit(_to_root=True)
~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "", line 2, in commit
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
ret_value = fn(self, *arg, **kw)
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/session.py", line 1323, in commit
trans.commit()
~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2642, in commit
self._do_commit()
~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2747, in _do_commit
self._connection_commit_impl()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2718, in _connection_commit_impl
self.connection._commit_impl()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1149, in _commit_impl
self._handle_dbapi_exception(e, None, None, None, None)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1147, in _commit_impl
self.engine.dialect.do_commit(self.connection)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/default.py", line 715, in do_commit
dbapi_connection.commit()
~~~~~~~~~~~~~~~~~~~~~~~^^
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Checklist
TagStudio Version
main branch commit 6aa0cf7
Operating System & Version
NixOS 26.05
Description
My initial discovery of the issue was that I went to use the "fix unlinked" tool and after pressing "Refresh" and then "Search & Relink" I can see in the logs that it's failing the inserts with "Database is locked" error.
After shutting it down and restarting and the refreshing again I closed the tool and now I can't add any tags because of "database is locked" errors.
I then rolled back to before #1432 as it seemed like the latest change, and now it works. I believe that the issue might stem from the new implementation of
all_entriesinsrc/tagstudio/core/library/alchemy/library.pywhere I believe it's loosing track of a session that can't be flushed/closed.This I believe happens in:
where the inner function:
is a generator that holds onto the other session.
This generator is then used in the unlinking refresh to find all the entries, but the session is never closed because it has left the original scope. This then puts the database in a deadlock, and can only be freed by killing the application.
I also think this verifies the suspicion that the test in
tests/macros/test_missing_files.pydoesn't work.Expected Behavior
I should be able to refresh and relink entries.
Steps to Reproduce
Logs
2026-07-14 01:35:27 [info ] [Library] Library DB version: 202
2026-07-14 01:35:27 [info ] [Library] Creating DB tables...
2026-07-14 01:35:27 [info ] [Library] Library migrated to DB version 202
2026-07-14 01:35:27 [info ] [Config] Thumbnail Cache Size: 1.02 GB
2026-07-14 01:35:27 [info ] [Ignore] Processing the .ts_ignore file... last_mtime=None library=PosixPath('/TagStudio/test-library') new_mtime=1765618780.0270758
2026-07-14 01:35:27 [info ] [Ignore] glob_patterns=[]
2026-07-14 01:35:27 [info ] [Ignore] No updates to the .ts_ignore detected last_mtime=1765618780.0270758 library=PosixPath('/TagStudio/test-library') new_mtime=1765618780.0270758
2026-07-14 01:35:27 [info ] searching library filter=BrowsingState(page_index=0, page_positions={}, sorting_mode=<SortingModeEnum.DATE_ADDED: 'file.date_added'>, ascending=False, random_seed=0, show_hidden_entries=False, query=None) query_full='SELECT DISTINCT entries.id \nFROM entries \nWHERE (entries.id NOT IN (SELECT tag_entries.entry_id \nFROM tag_entries \nWHERE tag_entries.tag_id IN (SELECT tags.id \nFROM tags \nWHERE tags.is_hidden))) ORDER BY entries.id DESC'
2026-07-14 01:35:27 [info ] SQL Execution finished (0 seconds)
2026-07-14 01:35:27 [info ] items to render count=8
2026-07-14 01:35:32 [info ] Closing Library...
2026-07-14 01:35:32 [info ] [Library] Creating SQLAlchemy Engine connection_string=sqlite:////.TagStudio/ts_library.sqlite poolclass=<class 'sqlalchemy.pool.impl.NullPool'>
2026-07-14 01:35:32 [info ] [Library] Opening SQLite Library library_dir=PosixPath('')
2026-07-14 01:35:32 [info ] [Library] Library DB version: 202
2026-07-14 01:35:32 [info ] [Library] Creating DB tables...
2026-07-14 01:35:32 [info ] [Library] Library migrated to DB version 202
2026-07-14 01:35:33 [info ] [Config] Thumbnail Cache Size: 1.02 GB
2026-07-14 01:35:33 [info ] [Ignore] Processing the .ts_ignore file... last_mtime=1765618780.0270758 library=PosixPath('') new_mtime=1763158157.844917
2026-07-14 01:35:33 [info ] [Ignore] glob_patterns=[]
2026-07-14 01:35:33 [info ] [Ignore] No updates to the .ts_ignore detected last_mtime=1763158157.844917 library=PosixPath('') new_mtime=1763158157.844917
2026-07-14 01:35:33 [info ] searching library filter=BrowsingState(page_index=0, page_positions={}, sorting_mode=<SortingModeEnum.DATE_ADDED: 'file.date_added'>, ascending=False, random_seed=0, show_hidden_entries=False, query=None) query_full='SELECT DISTINCT entries.id \nFROM entries \nWHERE (entries.id NOT IN (SELECT tag_entries.entry_id \nFROM tag_entries \nWHERE tag_entries.tag_id IN (SELECT tags.id \nFROM tags \nWHERE tags.is_hidden))) ORDER BY entries.id DESC'
2026-07-14 01:35:33 [info ] SQL Execution finished (0.32 seconds)
2026-07-14 01:35:33 [info ] items to render count=193275
2026-07-14 01:35:38 [info ] [UnlinkedRegistry] Refreshing unlinked files...
2026-07-14 01:35:58 [info ] [QtDriver] Selecting Items: append=False bridge=False item_id=253255
2026-07-14 01:35:58 [warning ] [FieldContainers] Updating Selection entry_id=253255
2026-07-14 01:35:58 [info ] [Library] Time it took to get entry: 0.01 seconds with_fields=True with_tags=True
2026-07-14 01:35:58 [info ] [emit_badge_signals] Emitting emit_on_absent=True tag_ids=set()
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.ARCHIVED: 'Archived'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.FAVORITE: 'Favorite'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver] Selecting Items: append=False bridge=False item_id=253255
2026-07-14 01:35:58 [warning ] [FieldContainers] Updating Selection entry_id=253255
2026-07-14 01:35:58 [info ] [Library] Time it took to get entry: 0 seconds with_fields=True with_tags=True
2026-07-14 01:35:58 [info ] [emit_badge_signals] Emitting emit_on_absent=True tag_ids=set()
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.ARCHIVED: 'Archived'>: False} origin_id=0
2026-07-14 01:35:58 [info ] [QtDriver][update_badges] Updating ItemThumb badges add_tags=False badge_values={<BadgeType.FAVORITE: 'Favorite'>: False} origin_id=0
2026-07-14 01:35:59 [info ] [SearchPanel] Updating items limit=25
2026-07-14 01:36:01 [info ] searching tags limit=25 results=2 search=photo statement="SELECT tag_aliases.tag_id, tag_aliases.name \nFROM tag_aliases \nWHERE (lower(tag_aliases.name) LIKE '%' || lower(:name_1) || '%')"
2026-07-14 01:36:01 [info ] [SearchPanel] Search results results=[<Tag ID: 1200 Name: Photo>, <Tag ID: 2637 Name: AI Generated Photo>, <Tag ID: 1207 Name: Avatar>, <Tag ID: 1201 Name: Banner>, <Tag ID: 1243 Name: Selfie>]
2026-07-14 01:36:03 [info ] [FieldContainers][add_tags_to_selected] selected=[253255] tag_ids=[1200]
2026-07-14 01:36:03 [info ] [Library][add_tags_to_entries] entry_ids=[253255] tag_ids=[1200]
Traceback (most recent call last):
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1147, in _commit_impl
self.engine.dialect.do_commit(self.connection)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/default.py", line 715, in do_commit
dbapi_connection.commit()
~~~~~~~~~~~~~~~~~~~~~~~^^
sqlite3.OperationalError: database is locked
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/TagStudio/src/tagstudio/qt/controllers/preview_panel_controller.py", line 56, in _add_tag_to_selected
self._containers.add_tags_to_selected(tag_id)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/TagStudio/src/tagstudio/qt/mixed/field_containers.py", line 252, in add_tags_to_selected
self.driver.add_tags_to_selected_callback(tag_ids)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "/TagStudio/src/tagstudio/qt/ts_qt.py", line 912, in add_tags_to_selected_callback
self.lib.add_tags_to_entries(selected, tag_ids)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/TagStudio/src/tagstudio/core/library/alchemy/library.py", line 1769, in add_tags_to_entries
session.commit()
~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/session.py", line 2035, in commit
trans.commit(_to_root=True)
~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "", line 2, in commit
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
ret_value = fn(self, *arg, **kw)
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/orm/session.py", line 1323, in commit
trans.commit()
~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2642, in commit
self._do_commit()
~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2747, in _do_commit
self._connection_commit_impl()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2718, in _connection_commit_impl
self.connection._commit_impl()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1149, in _commit_impl
self._handle_dbapi_exception(e, None, None, None, None)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/base.py", line 1147, in _commit_impl
self.engine.dialect.do_commit(self.connection)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/TagStudio/.venv/lib/python3.13/site-packages/sqlalchemy/engine/default.py", line 715, in do_commit
dbapi_connection.commit()
~~~~~~~~~~~~~~~~~~~~~~~^^
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked
(Background on this error at: https://sqlalche.me/e/20/e3q8)