Files
multi-agent-mux/.agents/reports/reviewer-agy-01/report-2fff60bd.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

65 lines
6.3 KiB
Markdown

# Review Report — PR #59 (Issue #31: Complete Claude purge, residual reporting, and live-process stop gate)
- **Reviewer**: `agy` (`herdr:reviewer-agy-01`)
- **Job ID**: `2fff60bd`
- **Branch / Range**: `fix/issue-31` (`26e2c43753169671b600d03274901dbe203988dd..6f4f40a4adb2115d5997dffd31fb6473f05cab68`, 13 commits, 12 files)
- **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261016T003524Z-a51548/1`
- **Platform**: macOS Darwin (`arm64-apple-darwin25`, `/usr/bin/python3` 3.9.6, `.venv/bin/python` 3.9.6, `/bin/bash` 3.2.57)
---
## 1. Executive Summary
PR #59 resolves Issue #31 by unifying the Claude adapter's owned-path resolution (`_owned_paths`) across `purge_artifacts`, `has_artifacts`, and `residual_artifacts`, covering:
- `projects/<key>/<uuid>.jsonl` and `projects/<key>/<uuid>/` (across both `ctx.claude_dir` and `<config_dir>/projects`),
- `file-history/<uuid>/` and `session-env/<uuid>/`,
- `jobs/<uuid[:8]>/` (only when `state.json` has `sessionId == uuid`, removing `state.json` last so partial failure preserves ownership evidence),
- `sessions/<pid>.<64hex>.key` (masked in output as `<pid>.*.key`) and `sessions/<pid>.json` for dead or PID-reused processes (removing keys before the `.json` file so ownership evidence survives partial failure),
- `<tmp>/claude-<uid>/<key>/<uuid>/` across `CLAUDE_CODE_TMPDIR`, `TMPDIR`/`TMP`/`TEMP`, and `/tmp`.
It also adds `residual_artifacts` for `claude`, `codex`, `grok`, and `opencode`; wires `stop_session.sh` to report residuals after `--purge-conversation` and to check `python -m lib_py.agents in-use <agent> <uuid> <cwd> --wait 5` before purging (exiting 4 without mutating state if a live agent process still holds the conversation); supports `CLAUDE_CONFIG_DIR` across `lib.sh`, `DiscoveryContext`, `claude.py`, and `_agent_home_guard.py`; and updates `multi-agent-mux-stop/SKILL.md` + `tests/data/skill_code_blocks.json` to reflect that `--purge-conversation` removes the session row rather than leaving `status: terminated`.
All 12 modified files pass Python 3.9.6 AST compilation and Bash 3.2 (`set -u`) syntax checks, all 11 regression test cases fail against base `26e2c43` and pass on head `6f4f40a`, and 217 tests across 6 test suites pass on macOS.
---
## 2. macOS Environment & Deletion-Scope Safety Verification
1. **BSD `ps -o lstart=` Format, Locale (`LC_ALL=C`), and `startedAt + 2` Comparison on macOS**:
- Verified BSD `ps -p <pid> -o lstart=` on this Mac under `LC_ALL=C`: outputs `'Fri Oct 16 09:48:41 2026 \n'`.
- `" ".join(out.split())` normalizes multi-space padding (including single-digit days like `'Oct 2'`) and trailing whitespace before `time.strptime(..., "%a %b %d %H:%M:%S %Y")`.
- Inspected 4 live `~/.claude/sessions/<pid>.json` files on this Mac (read-only): `startedAt / 1000.0 - lstart` ranged from `+0.694 s` to `+2.430 s`, so `lstart <= started + 2` holds for all live processes and rejects reused PIDs started after the session file was written.
- Fail-safe behavior: invalid/non-positive PIDs, `started <= 0`, missing `startedAt`, or `ps`/parse errors all return `True` (treated as live, refusing purge).
2. **macOS Temp Root Resolution (`TMPDIR` vs `/tmp` -> `/private/tmp`) & Real Layout Agreement**:
- On this Mac, `TMPDIR` is `/var/folders/q_/.../T/` (which has no `claude-501`), while Claude Code's actual scratchpad directory is `/private/tmp/claude-501/<workspace_key>/<uuid>/` (accessed via `/tmp/claude-501` symlink).
- `_tmp_roots()` checks `CLAUDE_CODE_TMPDIR` (both `<override>/claude-<uid>` and `<override>` when already named `claude-<uid>`), `TMPDIR`/`TMP`/`TEMP`, and `/tmp/claude-<uid>`, deduplicating via `os.path.realpath(c)`. This ensures `/private/tmp/claude-<uid>` is always discovered on macOS even when `TMPDIR` points to `/var/folders/.../T/`.
- Read-only inspection of `~/.claude` on this Mac confirmed exact structural agreement for `projects/<key>/`, `file-history/<uuid>/`, `session-env/<uuid>/`, `sessions/<pid>.json` + `<pid>.<64hex>.key`, and `jobs/<uuid[:8]>/state.json` (while shared files like `jobs/pins.json` and `history.jsonl` are untouched).
3. **Deletion Scope Isolation, Malformed UUID Rejection, and Symlink Safety**:
- `_plausible_id(uuid)` rejects `""`, `"."`, `".."`, `"*"`, and any string containing `"/"`, `".."`, or `"*"` before constructing any path or scanning any directory.
- `_owned_job` checks `json.load(open(job/state.json)).get("sessionId") == uuid`, leaving another session's `jobs/<uuid[:8]>/` (and `jobs/pins.json`) untouched.
- `_key_files` matches `rf"{stem}\.[0-9a-f]{{64}}\.key"` only for a `sessions/<pid>.json` whose `sessionId == uuid`, never reading key contents and never touching lone keys or other sessions' PIDs.
- `_remove` checks `if os.path.islink(path) or not os.path.isdir(path): os.remove(path)` before `shutil.rmtree(path, ignore_errors=True)`, and `scan_names` uses `os.walk(root)` (`followlinks=False`), ensuring symlinks are never traversed outside the target scope.
4. **Stop Exit 4 Contract, Residual Reporting, and Documentation Lockstep**:
- `stop_session.sh` runs `"$(_delegate_py_bin)" -m lib_py.agents in-use "$AGENT" "$PURGE_UUID" "$TARGET_CWD" --wait 5` before `atomic_dump_yaml`; if non-zero, it publishes a `cancelled` job event and exits `4` with all records and files preserved.
- `tests/data/skill_code_blocks.json` hashes (`301c347ff881`, `a973676cb2d1`) and line number (`stop_session.sh:327`) match `multi-agent-mux-stop/SKILL.md` and `stop_session.sh` (`test_issue42_skill_blocks.py` passes).
---
## 3. Test Suite Execution Summary
Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261016T003524Z-a51548/1` with `HERDR_*`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `MAM_JOBS_DIR`, `DELEGATE_JOB_LOGS_DIR`, and `CLAUDE_CONFIG_DIR` scrubbed, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp=/tmp/mam_verify_pr59_bt`:
- `tests/test_issue31_purge_residual.py`: **32 passed** (11 core regression cases verified to fail on base `26e2c43`)
- `tests/test_issue55_agent_homes.py`: **11 passed**
- `tests/test_issue42_skill_blocks.py`: **15 passed**
- `tests/test_a4_adapter_contract.py`: **55 passed**
- `tests/test_orc_onboard.py`: **37 passed, 1 skipped**
- `tests/test_tier1_unit.py`: **67 passed**
Total: **217 passed, 1 skipped, 0 failed**. Verification worktree `git status -s` is empty and no `.mam` directory was created.
[VERDICT: PASS]