mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
fix(kanban): clear _INITIALIZED_PATHS in remove_board so recycled DBs re-init schema
Archiving or deleting a board via remove_board() leaves the path's
"schema already initialized" entry in the module-level cache. A
concurrent connect(board=<slug>) call (e.g. the dashboard event-stream
poll loop) then:
1. resolves the same kanban.db path,
2. recreates the directory + an empty sqlite file because
connect() does mkdir(parents=True, exist_ok=True),
3. skips the CREATE TABLE pass because the cache entry says the
schema is already in place,
4. errors on the next read with `no such table: task_events`.
Drop the cache entry before mutating the filesystem so the fresh file
gets a proper schema init on next connect(). Applies to both
archive=True (rename) and archive=False (rmtree) branches.
Fixes #23833.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -534,6 +534,11 @@ def remove_board(slug: str, *, archive: bool = True) -> dict:
|
||||
if get_current_board() == normed:
|
||||
clear_current_board()
|
||||
|
||||
# A concurrent connect(board=normed) after the rename/delete recreates
|
||||
# an empty sqlite file via mkdir(exist_ok=True); the cache entry must be
|
||||
# dropped first so the schema init pass re-runs on that fresh file.
|
||||
_INITIALIZED_PATHS.discard(str((d / "kanban.db").resolve()))
|
||||
|
||||
if archive:
|
||||
archive_root = boards_root() / "_archived"
|
||||
archive_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -258,6 +258,37 @@ class TestBoardCRUD:
|
||||
kb.remove_board("pinned")
|
||||
assert kb.get_current_board() == "default"
|
||||
|
||||
@pytest.mark.parametrize("archive", [True, False])
|
||||
def test_remove_clears_init_cache_for_recreated_db(self, fresh_home, archive):
|
||||
# Regression for #23833: poll loops that call connect(board=slug) right
|
||||
# after remove_board() recreate an empty kanban.db at the same path
|
||||
# (connect() does mkdir(exist_ok=True)). If _INITIALIZED_PATHS still
|
||||
# contains the resolved path, the CREATE TABLE pass is skipped and
|
||||
# downstream readers hit `no such table: task_events`.
|
||||
kb.create_board("recycle")
|
||||
# First connect populates _INITIALIZED_PATHS for this DB.
|
||||
with kb.connect(board="recycle") as conn:
|
||||
kb.create_task(conn, title="t1", assignee="dev")
|
||||
db_path = kb.board_dir("recycle") / "kanban.db"
|
||||
assert str(db_path.resolve()) in kb._INITIALIZED_PATHS
|
||||
|
||||
kb.remove_board("recycle", archive=archive)
|
||||
# remove_board must drop the cache entry so a re-create through
|
||||
# connect() gets a fresh schema-init pass.
|
||||
assert str(db_path.resolve()) not in kb._INITIALIZED_PATHS
|
||||
|
||||
# Simulate the event-stream poll: re-open the same slug. connect()
|
||||
# recreates the directory + empty .db; the schema must be re-applied.
|
||||
with kb.connect(board="recycle") as conn:
|
||||
tables = {
|
||||
row[0]
|
||||
for row in conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table'"
|
||||
)
|
||||
}
|
||||
assert "task_events" in tables
|
||||
assert "tasks" in tables
|
||||
|
||||
def test_rename_updates_metadata(self, fresh_home):
|
||||
kb.create_board("slug-immutable")
|
||||
kb.write_board_metadata("slug-immutable", name="New Display Name")
|
||||
|
||||
Reference in New Issue
Block a user