Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-778fc866.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

9.7 KiB
Raw Blame History

리뷰 리포트 — PR #59 2차 (Issue #31) / job 778fc866

  • 리뷰어: planner-reviewer-claude-01 (Opus 5.5)
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261017T135731Z-e93f0a/1
  • identity: HEAD 2a8d73825b3832376aeaf96b09548359dc3cb887, tree d146f1ac68bcc3485d24fb92edb87264e4d1e8cb 일치. 트리는 clean이고 .mam 없음(테스트 후에도 동일).
  • 범위: 26e2c43..2a8d738 누적, 중점은 1차 이후 커밋 11개(26fb1c4..2a8d738)

요약

1차 수정 요청 3건(차단 F1, P1, P2)은 제품 코드에서 모두 해결됐다. 비차단 메모(서머타임/UTC, exit 4 안내, SKILL.md 문구, 테스트의 실제 /tmp 나열)도 해결됐다. 샌드박스 재현과 변형(mutation) 대조로 각 수정과 회귀 테스트가 실제로 맞물리는 것도 확인했다.

그러나 새로 추가된 테스트 test_any_spelling_of_the_uuid_is_the_lowercase_session[upper|mixed] 는 이 Mac 에서 항상 실패한다(T1). 원인은 제품 코드가 아니라 테스트의 macOS 이식성 버그다. 이대로 병합하면 이 Mac 의 스위트가 빨간불이 되므로 NOT PASS 로 판정한다. 수정은 테스트 한 줄이다.

차단 결함

T1. sh -c "sleep 120" <uuid> 보유자 프로세스가 macOS 에서 uuid 를 argv 에서 잃는다

  • 위치: tests/test_issue31_purge_residual.py:315-318
    named = subprocess.Popen(["sh", "-c", "sleep 120", conv])
    time.sleep(0.05)
    assert adapter.conversation_in_use(other, ctx) is True, "a command line naming the lowercase id"
    
  • 원인: 이 Mac 의 /bin/sh 는 GNU bash 3.2.57 이다. bash 는 -c 의 단일 단순 명령을 exec 로 바꿔 실행하므로, 실행된 프로세스의 argv 는 sleep 120 만 남고 $0 인 uuid 는 사라진다.
    • 직접 확인: /bin/sh -c 'sleep 30' MARKER-abc & → ps 에 sleep 30 만 보인다.
    • /bin/sh -c 'sleep 30; :' MARKER-abc → /bin/sh -c sleep 30; : MARKER-abc 로 uuid 가 남는다.
    • 이 저장소도 같은 함정을 이미 기록해 두었다: tests/test_issue10_13_purge.py:114 ("a shell execs its last simple command and the uuid …").
  • 재현: 검증 경로의 관련 모듈 실행(아래)에서 2 failed. 수정하지 않은 git clone 사본에서 -k any_spelling 만 돌려도 2 failed: AssertionError: a command line naming the lowercase id … conversation_in_use('91151A0D-0114-4C3C-a120-aec043818d1f', …) → False 아래 변형 대조 4종(F1/P1/P2/UTC)에서도 변형과 무관하게 같은 2개가 실패했다.
  • 제품 로직은 정상이다. argv 를 유지하는 보유자(["sh","-c","sleep 30; :", <lowercase uuid>])로 샌드박스에서 확인했다:
    • conversation_in_use(UPPER) = True, conversation_in_use(lower) = True.
    • 단일 명령 보유자로는 두 철자 모두 False.
  • 제안: 보유자를 ["sh", "-c", "sleep 120; :", conv] 나 [sys.executable, "-c", "import time; time.sleep(120)", conv] 처럼 uuid 가 argv 에 남는 형태로 바꾼다. 고정 time.sleep(0.05) 대신 짧은 폴링(예: 최대 2 s 동안 ps 에 보일 때까지)으로 기다리면 느린 호스트의 경합도 피할 수 있다.

1차 지적사항 확인

샌드박스 조건: HOME·CLAUDE_PROJECT_DIR·CLAUDE_CODE_TMPDIR·TMPDIR 를 모두 스크래치패드 아래로 두고, CLAUDE_CONFIG_DIR 은 비웠다. 인터프리터는 /usr/bin/python3 3.9.6. 실제 경로는 읽기만 했다.

  1. [차단] 비-UUID id — 해결.
    • 방식: _canonical()(claude.py:8-19)이 정규 UUID 만 받아 소문자로 바꾼다. _candidates·purge_artifacts·residual_artifacts·conversation_in_use 의 모든 입구가 이 함수를 거친다.
    • 재현: 아래 14개 id 는 purge 결과가 모두 rc 5 이고, 아무것도 지우지 않았다. projects/<key>/memory/MEMORY.md 와 다른 세션 파일은 전부 남았다.
      • "", ., .., a/b, *, ../x
      • memory, MEMORY, subagents, <key>
      • uuid 앞 8자리, <U>x, " <U>", "<U>\n"
    • 대문자 uuid 로 purge 하면 소문자 경로 9개가 정확히 지워진다(lower jsonl gone True). 다른 세션·공유 파일은 모두 보존됐다.
    • 변형 대조(_canonical 을 옛 _plausible_id 규칙으로 되돌림): rejects_malformed_uuid[memory, ' ', notes, …g, '{conv} ', 'x{conv}'], a_non_uuid_id_reaches_nothing[uuid-c, memory, notes], any_spelling 이 실패해 11 failed.
  2. [P1] 링크 경계 — 해결.
    • 방식: _inside()(claude.py:109-123)는 루트와 대상 사이의 디렉터리 구성요소를 lstat 으로 하나씩 확인하고, 부모의 realpath 가 루트의 realpath 아래에 있는지 본다.
    • 링크로 연결된 tmp <key>, file-history/, projects/<key> 뒤의 내용은 모두 보존되고 residual 로 보고된다.
    • 말단 링크(session-env/<U> → 외부)는 링크만 지워지고 대상 파일은 남는다.
    • 루트 자체가 링크인 경우(~/.claude → realhome, CLAUDE_CODE_TMPDIR → realtmp, 즉 /tmp → /private/tmp 같은 형태)는 정상적으로 purge 된다.
    • Python 3.9 macOS 의 shutil._use_fd_functions 는 True 로, rmtree 가 내부 링크를 따라가지 않는다.
    • 변형 대조(_inside → 항상 True): never_follows_a_linked_dir_below_a_root, reports_a_linked_orphan_it_will_not_remove 실패(T1 의 2개 포함 4 failed).
  3. [P2] 유한하지 않은 startedAt — 해결.
    • 방식: claude.py:84-96.
    • 살아 있는 sleep 보유자에 대해 startedAt 을 아래 9가지 값으로 바꿔 봤다. 모두 in-use rc 4, purge rc 4 였고 jsonl 은 보존됐다.
      • NaN, Infinity, -Infinity, "NaN", true, 1e400, 400자리 정수, null, "1760000000000"
    • 정상 ms 값이면 live(rc 4), 1시간 전 값(pid 재사용)이면 free 다.
    • 변형 대조(isfinite 제거): unusable_started_at_means_live[nan], live_holder…[NaN] 실패. inf 계열은 다른 분기에서도 live 로 판정되므로 이 변형으로는 실패하지 않는 것이 맞다.

비차단 메모 확인

  • UTC (af8ed39): 실제 살아 있는 claude 4개를 읽기 전용으로 비교했다(TZ=UTC0 LC_ALL=C ps -o lstart= + calendar.timegm).
    • lstart 가 세션 파일의 procStart(UTC)와 초 단위까지 일치했다. 예: Sat Oct 17 13:55:10 2026.
    • startedAt − lstart 는 1.34 / 1.49 / 1.99 / 2.36 s 로, 모두 live 판정이다.
    • 변형 대조(mktime·로컬 TZ 로 되돌림): liveness_reads_lstart_as_utc 실패.
  • exit 4 안내 (e156179): in-use stderr 가 두 가지를 출력한다.
    • "a claude process whose session file under /sessions/ names it" (CLAUDE_CONFIG_DIR 을 반영)
    • "ps -axww | grep "
    • stop_session.sh:292 의 h="$(… 2>&1 >/dev/null)" || rc=$? 는 /bin/bash 3.2 + set -euo pipefail 에서 rc 4 와 안내 문구를 올바르게 잡고, 안내가 비었을 때도 스크립트가 계속 진행된다(직접 확인). bash -n 통과.
  • SKILL.md exit 4 문구: "Records preserved … the session itself may already be down" 으로 정확해졌다.
  • 테스트의 실제 /tmp (26fb1c4): autouse 픽스처가 _SYSTEM_TMP 를 테스트 디렉터리로 패치하고, test_tests_never_reach_the_real_tmp_root 가 하위 프로세스까지 확인한다.
  • agy 게이트: 동작 변화는 그대로다(fail-closed). 실제 agy 로는 확인하지 않았다(브리프상 금지).

회귀 관점 (dd292c1 등)

  • has_artifacts 는 링크 뒤의 후보도 포함하므로 purge auto 가 claude 를 고른다. 이때 purge 는 아무것도 지우지 않고 residual 만 보고한다(위 links 재현: rc 0, purged 에는 말단 링크 1개만 있고 residual 4개). 의도대로다.
  • 동작이 바뀌는 지점이 하나 있다(비차단): projects/<key> 자체가 심볼릭 링크인 사용자는 이제 transcript jsonl 이 purge 되지 않고 residual 로만 보고된다. 반면 discover/verify_artifact 는 링크를 따라가 그 대화를 계속 찾는다. P1 요구("링크를 따라가지 않음")의 결과이고 residual 로 드러나므로 수용 가능하지만, SKILL.md 에 한 줄 적어 두면 좋다.
  • 철자 비대칭(비차단): 소문자 id 로 질의하면 대문자 argv 보유자는 보지 못한다(claude -r <UPPER> 로 띄운 경우). claude 가 소문자로 기록하는 한 실질 영향은 없다.
  • base.in_use_hints 추가와 __main__ 의 in-use stderr 출력은 다른 어댑터에 기본 안내만 준다. 회귀 없음.
  • test_a4_adapter_contract 의 claude id 를 실제 uuid 로 바꾼 것은 새 계약(정규 UUID 만 허용)에 따른 정당한 갱신이다.
  • Python 3.9 파싱(feature_version=(3,9))과 /usr/bin/python3 실행(모든 프로브)에서 문제 없었다.

테스트

  • 검증 경로, env -i(HERDR_·WORKSPACE_ROOT·MAM_·DELEGATE_JOB_LOGS_DIR·CLAUDE_CONFIG_DIR 미설정), 스크래치패드 basetemp, PYTHONDONTWRITEBYTECODE=1 -p no:cacheprovider.
    • 실행 모듈: test_issue31_purge_residual, test_issue10_13_purge, test_a4_adapter_contract, test_issue42_skill_blocks, test_issue55_agent_homes, test_orc_onboard, test_uuid_target, test_tier1_unit, test_tier2_component
    • 결과: 2 failed (T1), 330 passed, 1 skipped (630 s).
  • 사본에서 test_issue31_purge_residual.py 를 반복 실행한 결과:
    • 비변형 사본: 2 failed (T1) / 65 passed.
    • P2 변형 사본: 첫 실행에서 test_grok_residual_reports_memory_observations 가 한 번 ERROR 였다. 이후 재실행 4회에서는 재현되지 않아 일회성으로 보고만 한다.
  • 6f4f40a 의 제품 파일로 통째로 되돌린 대조는 쓸 수 없었다. autouse 픽스처가 26fb1c4 에서 추가된 claude._SYSTEM_TMP 를 패치하므로 67개 모두 setup ERROR 가 난다. 그래서 위의 수정별 변형 대조로 대신했다.

[VERDICT: NOT PASS]