diff --git a/src/claude_agent_sdk/_internal/session_mutations.py b/src/claude_agent_sdk/_internal/session_mutations.py index 9350123d..7d68d3c4 100644 --- a/src/claude_agent_sdk/_internal/session_mutations.py +++ b/src/claude_agent_sdk/_internal/session_mutations.py @@ -22,6 +22,7 @@ import json import os import re +import shutil import unicodedata import uuid as uuid_mod from dataclasses import dataclass @@ -170,11 +171,13 @@ def delete_session( session_id: str, directory: str | None = None, ) -> None: - """Delete a session by removing its JSONL file. + """Delete a session by removing its JSONL file and subagent transcripts. - This is a hard delete — the file is removed permanently. SDK users who - need soft-delete semantics can use ``tag_session(id, '__hidden')`` and - filter on listing instead. + This is a hard delete — the ``{session_id}.jsonl`` file is removed + permanently, along with the sibling ``{session_id}/`` subdirectory that + holds subagent transcripts (if it exists). SDK users who need soft-delete + semantics can use ``tag_session(id, '__hidden')`` and filter on listing + instead. Args: session_id: UUID of the session to delete. @@ -206,6 +209,8 @@ def delete_session( if e.errno == errno.ENOENT: raise FileNotFoundError(f"Session {session_id} not found") from e raise + # Subagent transcripts live in a sibling {session_id}/ dir; often absent. + shutil.rmtree(path.parent / session_id, ignore_errors=True) @dataclass diff --git a/tests/test_session_mutations.py b/tests/test_session_mutations.py index 0575e5e3..18ad8aeb 100644 --- a/tests/test_session_mutations.py +++ b/tests/test_session_mutations.py @@ -490,6 +490,25 @@ def test_deletes_session_file(self, claude_config_dir: Path, tmp_path: Path): delete_session(sid, directory=project_path) assert not file_path.exists() + def test_removes_subagent_transcript_dir( + self, claude_config_dir: Path, tmp_path: Path + ): + """Cascades the sibling {sid}/ subagent dir alongside the .jsonl.""" + project_path = str(tmp_path / "proj") + Path(project_path).mkdir(parents=True) + project_dir = _make_project_dir( + claude_config_dir, os.path.realpath(project_path) + ) + sid, file_path = _make_session_file(project_dir) + subagent_dir = project_dir / sid + subagent_dir.mkdir() + (subagent_dir / f"{uuid.uuid4()}.jsonl").write_text("{}\n") + + delete_session(sid, directory=project_path) + + assert not file_path.exists() + assert not subagent_dir.exists() + def test_deletes_without_directory(self, claude_config_dir: Path): """Searches all project directories when no directory is given.""" project_dir = _make_project_dir(claude_config_dir, "/any/project")