Files
multi-agent-mux/.agents/reports/reviewer-grok-01/report-f0df1be0.md
T
GodopuandClaude Opus 5.5 6fd417a7b7 release(v4.9.5): a claude purge removes every path the session owns (Issue #31, PR #59) - archive the review reports, bump the lockstep version, load logroot eagerly
Review loop (--review-only, --all-reviewer, focused on this Mac:
macOS, /usr/bin/python3 3.9.6, bash 3.2): round 1 (@6f4f40a) agy and
grok PASS, claude and codex NOT PASS (a non-uuid id `memory` purged the
auto-memory dir, a linked dir below a root was followed, a NaN
startedAt counted as dead), sent as a PR comment and fixed by the
author in eleven commits; round 2 (@2a8d738) confirmed all three fixed
but 4/4 NOT PASS on one test holder (`sh -c "sleep 120" <uuid>` loses
the uuid on macOS /bin/sh), fixed in de6f3fc; round 3 (@de6f3fc) 4/4
PASS.

Full suite on macOS / Python 3.9.6 @de6f3fc: 1934 passed, 56 skipped,
1 error (the known #55 false positive: agy conversations of another
workspace). The real ~/.claude, /private/tmp/claude-<uid> and herdr
panes unchanged.

Also names lib_py.delegation.logroot in eager.CORE_MODULES (the v4.9.4
review's non-blocking note).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-18 01:25:15 +09:00

48 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Review f0df1be0 — PR #59, Issue #31 claude purge
Reviewed only the snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261016T003524Z-a51548/1` (`loop-20261016T003524Z-a51548`). Head `6f4f40a4adb2115d5997dffd31fb6473f05cab68`, tree `f4d9422ad0c1d616b5f37065f3e96b34c28f78d8`, base `26e2c43753169671b600d03274901dbe203988dd` is an ancestor. Thirteen commits, 12 files, +1127/−27. `git diff --check` is clean. Snapshot porcelain is empty and the snapshot has no `.mam`. Control-plane main is still `26e2c43753169671b600d03274901dbe203988dd`, porcelain empty. No product file was edited.
Host tools: `/usr/bin/python3` 3.9.6, the control-plane venv the same 3.9.6, `/bin/bash` 3.2.57. `bash -n` accepts `stop_session.sh` and `lib.sh`. With `set -u`, the new `CLAUDE_PROJECT_DIR` default (`lib.sh:78`) expands to `$HOME/.claude/projects` when both variables are unset, and to `$CLAUDE_CONFIG_DIR/projects` when only the config dir is set.
Every purge, `in-use`, and `stop --purge-conversation` exercised here used a temporary `HOME`, `CLAUDE_CONFIG_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CODE_TMPDIR`, and `TMPDIR`. The real `~/.claude` and `/private/tmp/claude-<uid>` were listed with `ls`-level reads.
## What this Mac already stores
`~/.claude` has real directories `file-history`, `session-env`, `sessions`, `jobs`, and `projects`. `file-history` holds 18 uuid-named directories and no symlinks. `session-env` holds 21 uuid-named directories and no symlinks. `sessions` holds 5 `*.json` files and 5 key files; every key name matches `<pid>.<64 lowercase hex>.key`. Each json has integer `pid`, `sessionId`, and a millisecond `startedAt` (13 digits). The filename stem equals `pid` in all 5. All 5 pids were alive at the read. `jobs` holds 2 real directories, both 8 characters long, and each `state.json` `sessionId` starts with that directory name. No job entry is a symlink. `projects` has 16 keys, none of them symlinks; across those keys the entries include 25 `<uuid>.jsonl` files and 7 `<uuid>` directories, plus shared names such as the memory directory the purge leaves alone.
`/tmp` is a symlink to `/private/tmp`. `/tmp/claude-<uid>` and `/private/tmp/claude-<uid>` both exist and share one real path. Under that root, project keys are real directories. Their children are uuid directories (16) plus 3 other directories whose names are 57 or 58 alphanumeric characters, contain no uuid, and are siblings of the uuid scratchpads. `$TMPDIR/claude-<uid>` does not exist. `CLAUDE_CODE_TMPDIR` was unset in this review process. That is the layout `_tmp_roots` (`claude.py:25-48`) and `_owned_paths` (`claude.py:256-285`) assume: an existing per-uid root, then `<key>/<uuid>`.
## lstart on this host
`ps -p <pid> -o lstart=` with `LC_ALL=C` prints five fields. `time.strptime` of the collapsed line with `%a %b %d %H:%M:%S %Y` accepts both `Oct 5` and the BSD space-padded `Oct 5`. For the five live session files, `lstart - startedAt/1000` is between −2.43 s and −0.694 s, so `lstart <= startedAt/1000 + 2` (`claude.py:77`) holds for every one of them. Zero of those live files would be treated as a reused pid.
A temp-home probe of `_session_file_live`: the current pid with `startedAt` set to now is live; the same pid with `startedAt` 1.5 s after its own `lstart` is live; missing `startedAt`, `startedAt` 0, a non-numeric `startedAt`, `pid` 0, a negative pid, and boolean `True` are live. A dead pid is stale. The same live pid with `startedAt` an hour earlier, or 3 s earlier than its `lstart`, is stale. The process locale reported `en_US` / `UTF-8`, and the English month from `LC_ALL=C` parsed. A parse failure returns live (`claude.py:71-72`), which refuses the purge.
## Deletion stays on this session
On a temporary config root the purge removed the workspace `<uuid>.jsonl`, the sibling `<uuid>/` directory, `file-history/<uuid>/`, `session-env/<uuid>/`, `jobs/<uuid[:8]>/` whose `state.json` named the session, the dead `sessions/<pid>.json`, its `<pid>.<64 hex>.key`, and `<CLAUDE_CODE_TMPDIR>/claude-<uid>/<key>/<uuid>/`. The printed key label contains `<pid>.*.key` and the hex is absent from the purged list.
These stayed: another session's `sessions/<pid>.json` and key for a live pid, that session's scratchpad, `history.jsonl` whose body names the purged uuid, `projects/<key>/memory/MEMORY.md`, and a job directory whose 8-character name matches the uuid but whose `state.json` names a different session (`claude.py:244-254`). `_plausible_id` (`base.py:89-92`) rejects `""`, `.`, `..`, `*`, `../x`, `a/b`, and `....` (`".."` is a substring). Purging those ids left the tree unchanged.
Symlinks. A `file-history/<uuid>` symlink to an outside directory was unlinked; the outside `secret.txt` remained. A `projects/<key>/<uuid>` symlink to an outside directory was unlinked; the outside file remained. A directory symlink and a file symlink inside a real owned `<uuid>/` directory were removed with the directory; both targets remained (`_remove`, `claude.py:80-87`, uses `os.path.islink` and then `os.remove`; `shutil.rmtree` sees the real directory). A `jobs/<8>` symlink whose target `state.json` names the session was unlinked; the target directory and its `other.txt` remained (`_remove_job`, `claude.py:107-109`). A `sessions/<pid>.json` symlink to an outside json was unlinked after the read saw this session id; the outside file remained.
A project key under the temp root that is itself a symlink is followed: `<symlink>/<uuid>` was removed on the far side. That far-side directory is still the one named this session's uuid, which is the scratchpad path when a key has been relocated. The real per-uid temp root on this Mac has no key symlinks. The cross-session paths above stayed in the same probe.
A live session file is omitted from `_owned_paths` (`claude.py:274-278`). Calling `purge_artifacts` directly while that pid was alive left `sessions/<pid>.json` in place and removed `file-history/<uuid>/`. After the process exited, the same call removed the session file. The purge CLI does not take that direct path: `in-use` was true, the process exited 4, and the file-history marker was still there (`__main__.py:146-148`). `in-use` with no holder exits 0. `in-use` without a uuid exits 1.
## Stop
`stop_session.sh:285-300` runs `python -m lib_py.agents in-use <agent> <uuid> <cwd> --wait 5` before the registry write. Any status other than 0 exits 4 and leaves the row. The registry python then prints `purged:` lines and either `WARN residual:` or `residual: none (agent: <agent>)` (`stop_session.sh:355-368`). `lib.sh` exports `PYTHONPATH` to the skill dir at source time, so the new `in-use` module resolves the same way `exit-key` already does. `DELEGATE_JOB_ID`, `DELEGATE_JOB_TOKEN`, and `DELEGATE_JOB_ATTEMPT` are set before that call (`stop_session.sh:146-161`), including to empty strings, so `set -u` does not trip. The passthrough `raise SystemExit(1)` is now `stop_session.sh:327`, and `tests/data/skill_code_blocks.json` records that line. The stop SKILL.md diagram and the purge paragraph match the fenced blocks in that json (the skill-block test passed).
## Tests
Pytest used the control-plane venv, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp` `/tmp/mamf0-f0df1be0-work/pybase`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, `WORKSPACE_ROOT`, `DELEGATE_JOB_LOGS_DIR`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `CLAUDE_CODE_TMPDIR`, `CODEX_HOME`, and `PYTHONPATH` were dropped. The suite's own fixtures then point `HOME` at the test tmp.
On the snapshot, 80 tests passed and 1 skipped in 28.31 s: all of `tests/test_issue31_purge_residual.py` (32 tests), `tests/test_issue42_skill_blocks.py`, and `tests/test_issue55_agent_homes.py`. The skip is `test_issue42_skill_blocks.py:266`, because `.claude/skills` is absent in this worktree. The issue-31 file covers the owned-path purge, the untouched neighbor files, the same-prefix job, malformed ids, the live-process refusal, the reused pid, a key that cannot be unlinked, a job directory that cannot be emptied, `CLAUDE_CONFIG_DIR`, the temp-root rules, the stop exit 4, the stray-key `WARN residual:`, and codex, grok, and opencode residuals.
The same issue-31 file copied onto an archive of base `26e2c43` failed 24 tests and passed 8 in 17.09 s. The failures are behavioral. The old purge left `file-history/<uuid>` and `session-env/<uuid>` behind. The old purge CLI, asked about a live pid whose argv does not contain the uuid, removed the jsonl and returned `in_use` false. The old stop did not exit 4 for that live process and did not print `WARN residual:` for a transcript under another project key. Two failures are the new helpers `_session_file_live` and `_tmp_roots` being absent; the assertions above fail on the old behavior itself.
After the report, snapshot porcelain is still empty, the snapshot still has no `.mam`, and control-plane porcelain is still empty at `26e2c43`. The herdr config mtime is still 1784456394.
[VERDICT: PASS]