fix(agents): a claude purge takes directories only for a canonical uuid (Issue #31, Mac review)

projects/<key>/memory/ (Claude Code's auto memory) sits beside the session
dirs, and `purge claude memory` removed it whole. Only an 8-4-4-4-12 hex id
now reaches the session's directories, session files, jobs and tmp dirs. Any
other id keeps the pre-#31 reach, the `<id>.jsonl` file alone (test_a4 still
purges "uuid-c"); its residual is that file and nothing holds it in use.
The shared _plausible_id is unchanged for the other adapters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-16 03:19:02 +00:00
1 parent 26fb1c4130
commit bd09c6a133
2 files changed
+61 -5

No files matched your search

@@ -4,6 +4,16 @@ from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_
from lib_py.verify_session import workspace_key
_UUID = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.IGNORECASE)
def _is_uuid(uuid) -> bool:
"""claude names every session by a canonical uuid. Any other name in a
session's place is not one: projects/<key>/memory/ (auto memory) sits
beside the session dirs."""
return isinstance(uuid, str) and bool(_UUID.fullmatch(uuid))
def _config_dir(ctx: DiscoveryContext) -> str:
"""claude's config root: CLAUDE_CONFIG_DIR moves all of it (sessions/,
file-history/, session-env/, jobs/, projects/)."""
@@ -262,14 +272,19 @@ class ClaudeAgentAdapter(BaseAgentAdapter):
Shared files that merely mention it (history.jsonl, ~/.claude.json,
backups, paste-cache) are not the session's and are never listed."""
# Checked before any path is built: whole directories named by the id
# are removed, so "", "." or ".." would reach file-history/ itself.
# are removed, so "", "." or ".." would reach file-history/ itself,
# and "memory" the auto memory. An id that is not a uuid keeps the
# pre-#31 reach: the `<id>.jsonl` file alone.
if not _plausible_id(uuid):
return []
cfg = _config_dir(ctx)
paths = []
for pdir in _projects_dirs(ctx):
base = os.path.join(pdir, ctx.ws_key, uuid)
paths += [p for p in (base + ".jsonl", base) if os.path.lexists(p)]
paths += [p for p in ((base + ".jsonl", base) if _is_uuid(uuid) else (base + ".jsonl",))
if os.path.lexists(p)]
if not _is_uuid(uuid):
return paths
paths += [p for p in (os.path.join(cfg, "file-history", uuid),
os.path.join(cfg, "session-env", uuid)) if os.path.lexists(p)]
job = self._owned_job(uuid, ctx)
@@ -293,6 +308,10 @@ class ClaudeAgentAdapter(BaseAgentAdapter):
return bool(self._owned_paths(uuid, ctx))
def conversation_in_use(self, uuid: str, ctx: DiscoveryContext) -> bool:
# No claude process holds a name that is not a uuid; searching
# command lines for one ("a/b", "memory") only finds strangers.
if not _is_uuid(uuid):
return False
if conversation_in_use(uuid, [self.artifact_path(uuid, ctx)]):
return True
return any(_session_file_live(data, pid) for _, pid, data in self._session_files(uuid, ctx))
@@ -322,8 +341,8 @@ class ClaudeAgentAdapter(BaseAgentAdapter):
roots (two levels), plus the content-matched jobs dir and session
files with their (masked) key files. projects is not walked deeper;
it can be very large."""
if not _plausible_id(uuid):
return []
if not _is_uuid(uuid):
return self._owned_paths(uuid, ctx)
cfg = _config_dir(ctx)
pdirs = _projects_dirs(ctx)
found = set(scan_names(cfg, uuid, skip=pdirs))