From 8a4f651bdfa4bb042a5671cf0d2edbf1b5decd1f Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 15 Oct 2026 16:00:40 +0000 Subject: [PATCH] fix(agents): claude follows CLAUDE_CONFIG_DIR (Issue #31, review) CLAUDE_CONFIG_DIR moves claude's whole config root, sessions/ included, so a live session started under it was not seen and a stopped one left file-history and the rest behind. The config root is now CLAUDE_CONFIG_DIR, else /.claude; the purge and residual scans look at ctx.claude_dir and the root's own projects/, each once. The test guard drops the variable for every test like the other agent-home variables (test_issue55 l1 requires every *_DIR the adapters read to be accounted for). Co-Authored-By: Claude Opus 5.5 --- .../skills/lib_py/agents/adapters/claude.py | 56 ++++++++++++------- tests/_agent_home_guard.py | 2 +- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/.agents/skills/lib_py/agents/adapters/claude.py b/.agents/skills/lib_py/agents/adapters/claude.py index f08103a..6243d37 100644 --- a/.agents/skills/lib_py/agents/adapters/claude.py +++ b/.agents/skills/lib_py/agents/adapters/claude.py @@ -5,7 +5,21 @@ from lib_py.verify_session import workspace_key def _config_dir(ctx: DiscoveryContext) -> str: - return os.path.join(ctx.home_dir, ".claude") + """claude's config root: CLAUDE_CONFIG_DIR moves all of it (sessions/, + file-history/, session-env/, jobs/, projects/).""" + return os.environ.get("CLAUDE_CONFIG_DIR") or os.path.join(ctx.home_dir, ".claude") + + +def _projects_dirs(ctx: DiscoveryContext) -> list: + """ctx.claude_dir (CLAUDE_PROJECT_DIR, else /.claude/projects) and + the config root's own projects/, each once.""" + dirs, seen = [], set() + for d in (ctx.claude_dir, os.path.join(_config_dir(ctx), "projects")): + real = os.path.realpath(d) + if real not in seen: + seen.add(real) + dirs.append(d) + return dirs def _tmp_roots() -> list: @@ -211,10 +225,12 @@ class ClaudeAgentAdapter(BaseAgentAdapter): if not _plausible_id(uuid): return [] cfg = _config_dir(ctx) - jsonl = self.artifact_path(uuid, ctx) - paths = [p for p in (jsonl, jsonl[:-len(".jsonl")], - os.path.join(cfg, "file-history", uuid), - os.path.join(cfg, "session-env", uuid)) if os.path.lexists(p)] + 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 (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) if job: paths.append(job) @@ -264,20 +280,22 @@ class ClaudeAgentAdapter(BaseAgentAdapter): 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 + pdirs = _projects_dirs(ctx) + found = set(scan_names(cfg, uuid, skip=pdirs)) + for pdir in pdirs: + try: + keys = sorted(os.listdir(pdir)) + except OSError: + keys = [] + for key in keys: + kdir = os.path.join(pdir, 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) diff --git a/tests/_agent_home_guard.py b/tests/_agent_home_guard.py index ae50ab7..0b02d28 100644 --- a/tests/_agent_home_guard.py +++ b/tests/_agent_home_guard.py @@ -18,7 +18,7 @@ import tempfile # The pane-inherited variables conftest._isolate_agent_homes drops for every # test, so HOME (which the tests set) governs, as it does in a plain shell. -DROPPED_VARS = ("HOME_DIR", "CLAUDE_PROJECT_DIR", "CODEX_HOME", "LOCAL_BIN") +DROPPED_VARS = ("HOME_DIR", "CLAUDE_PROJECT_DIR", "CLAUDE_CONFIG_DIR", "CODEX_HOME", "LOCAL_BIN") # Read by the code under test but left alone, on purpose: HOME is where the # real herdr tests and the shim bootstrap find the server socket, and it is # what the suite's own tests set for an isolated home; XDG_CONFIG_HOME and