fix(issue-31): a claude purge removes every path the session owns, reports what is left, and waits for the process; the stop docs say a purge removes the row #59
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
import os, re, json, glob, time, shutil, subprocess
|
import os, re, json, glob, time, shutil, subprocess
|
||||||
from typing import Optional, Any
|
from typing import Optional, Any
|
||||||
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_in_use, _plausible_id
|
from lib_py.agents.base import BaseAgentAdapter, DiscoveryContext, conversation_in_use, scan_names, _plausible_id
|
||||||
from lib_py.verify_session import workspace_key
|
from lib_py.verify_session import workspace_key
|
||||||
|
|
||||||
|
|
||||||
@@ -256,6 +256,40 @@ class ClaudeAgentAdapter(BaseAgentAdapter):
|
|||||||
purged.append(_label(path))
|
purged.append(_label(path))
|
||||||
return purged
|
return purged
|
||||||
|
|
||||||
|
def residual_artifacts(self, uuid: str, ctx: DiscoveryContext) -> list:
|
||||||
|
"""What still carries this session: names holding the uuid under the
|
||||||
|
config root, in projects (two levels: key, then entry) and in the tmp
|
||||||
|
roots (two levels), plus the content-matched jobs dir and session
|
||||||
|
files. projects is not walked deeper; it can be very large."""
|
||||||
|
if not _plausible_id(uuid):
|
||||||
|
return []
|
||||||
|
cfg = _config_dir(ctx)
|
||||||
|
found = set(scan_names(cfg, uuid, skip=(os.path.join(cfg, "projects"), ctx.claude_dir)))
|
||||||
|
try:
|
||||||
|
keys = sorted(os.listdir(ctx.claude_dir))
|
||||||
|
except OSError:
|
||||||
|
keys = []
|
||||||
|
for key in keys:
|
||||||
|
kdir = os.path.join(ctx.claude_dir, key)
|
||||||
|
if uuid in key:
|
||||||
|
found.add(kdir)
|
||||||
|
elif os.path.isdir(kdir):
|
||||||
|
try:
|
||||||
|
found.update(os.path.join(kdir, n) for n in os.listdir(kdir) if uuid in n)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
job = self._owned_job(uuid, ctx)
|
||||||
|
if job:
|
||||||
|
found.add(job)
|
||||||
|
found.update(path for path, _, _ in self._session_files(uuid, ctx))
|
||||||
|
for root in _tmp_roots():
|
||||||
|
try:
|
||||||
|
tkeys = os.listdir(root)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
found.update(p for p in (os.path.join(root, k, uuid) for k in tkeys) if os.path.lexists(p))
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
|
||||||
if use_wrapper or not session_uuid:
|
if use_wrapper or not session_uuid:
|
||||||
return f"{binary} --dangerously-skip-permissions"
|
return f"{binary} --dangerously-skip-permissions"
|
||||||
|
|||||||
@@ -91,6 +91,22 @@ def _plausible_id(uuid: str) -> bool:
|
|||||||
return isinstance(uuid, str) and bool(uuid.strip(".")) and not any(s in uuid for s in ("/", "..", "*"))
|
return isinstance(uuid, str) and bool(uuid.strip(".")) and not any(s in uuid for s in ("/", "..", "*"))
|
||||||
|
|
||||||
|
|
||||||
|
def scan_names(root: str, uuid: str, skip=()) -> list:
|
||||||
|
"""Paths under `root` whose name carries `uuid` (Issue #31). A matched
|
||||||
|
directory is reported whole and not descended; `skip` dirs are not walked."""
|
||||||
|
if not _plausible_id(uuid) or not os.path.isdir(root):
|
||||||
|
return []
|
||||||
|
skip_real = {os.path.realpath(s) for s in skip if s}
|
||||||
|
found = []
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root):
|
||||||
|
for name in dirnames + filenames:
|
||||||
|
if uuid in name:
|
||||||
|
found.append(os.path.join(dirpath, name))
|
||||||
|
dirnames[:] = [d for d in dirnames if uuid not in d
|
||||||
|
and not (skip_real and os.path.realpath(os.path.join(dirpath, d)) in skip_real)]
|
||||||
|
return sorted(found)
|
||||||
|
|
||||||
|
|
||||||
class SpawnSpec:
|
class SpawnSpec:
|
||||||
def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None):
|
def __init__(self, agent_name: str, session_name: str, command: str, env: Optional[Dict[str, str]] = None):
|
||||||
self.agent_name = agent_name
|
self.agent_name = agent_name
|
||||||
|
|||||||
Reference in new issue
Block a user