Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/claude_agent_sdk/_internal/session_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import json
import os
import re
import shutil
import unicodedata
import uuid as uuid_mod
from dataclasses import dataclass
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/test_session_mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading