Guard chat deletion against empty context IDs

Reject empty and whitespace-only chat context identifiers before provider or filesystem cleanup begins. This prevents malformed deletion requests from resolving to the shared chat directory and adds focused regression coverage.

Alessandro committed Aug 12, 2026 at 02:01 UTC 1b4505d8e59f91cbd861ba6fe6c6924353a211a2
3 files changed +25
helpers/persist_chat.py
+4
@@ -161,6 +161,8 @@ def export_json_chat(context: AgentContext):
161
162 def remove_chat(ctxid):
163 """Remove a chat or task context"""
164 + if not isinstance(ctxid, str) or not ctxid.strip():
165 + raise ValueError("remove_chat: context id must not be empty")
166 _delete_provider_responses_for_chat(ctxid)
167 path = get_chat_folder_path(ctxid)
168 files.delete_dir(path)
@@ -168,6 +170,8 @@ def remove_chat(ctxid):
170
171 def remove_msg_files(ctxid):
172 """Remove all message files for a chat or task context"""
173 + if not isinstance(ctxid, str) or not ctxid.strip():
174 + raise ValueError("remove_msg_files: context id must not be empty")
175 path = get_chat_msg_files_folder(ctxid)
176 files.delete_dir(path)
177
helpers/persist_chat.py.dox.md
+1
@@ -47,6 +47,7 @@
47 - Chat loading skips directories that do not contain `chat.json`; malformed existing chat files still report load errors.
48 - Chat saves write and fsync a same-directory temporary file, atomically replace `chat.json`, and fsync the directory so an interrupted save cannot truncate the previous chat.
49 - Contexts are marked with private `SAVED_CHAT_CONTEXT_DATA_KEY` only after a successful save or load from disk so snapshot code can detect deleted chat files without hiding fresh unsaved chats.
50 +- Chat and message-file removal reject empty or whitespace-only context IDs before provider or filesystem deletion begins.
51
52 ## Key Concepts
53
tests/test_persist_chat_deletion_guards.py new
+20
@@ -0,0 +1,20 @@
1 +from __future__ import annotations
2 +
3 +import sys
4 +from pathlib import Path
5 +
6 +import pytest
7 +
8 +PROJECT_ROOT = Path(__file__).resolve().parents[1]
9 +if str(PROJECT_ROOT) not in sys.path:
10 + sys.path.insert(0, str(PROJECT_ROOT))
11 +
12 +from helpers import persist_chat
13 +
14 +
15 +@pytest.mark.parametrize("ctxid", [None, "", " "])
16 +def test_chat_deletion_rejects_empty_context_ids(ctxid) -> None:
17 + with pytest.raises(ValueError, match="context id must not be empty"):
18 + persist_chat.remove_chat(ctxid)
19 + with pytest.raises(ValueError, match="context id must not be empty"):
20 + persist_chat.remove_msg_files(ctxid)