Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-a59e709d.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.3 KiB

PR #59 2차 Mac 리뷰 — NOT PASS

  • Job: a59e709d; loop: loop-20261017T135731Z-e93f0a
  • Base: 26e2c43753169671b600d03274901dbe203988dd
  • Head: 2a8d73825b3832376aeaf96b09548359dc3cb887
  • Tree: d146f1ac68bcc3485d24fb92edb87264e4d1e8cb
  • 검증 트리: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261017T135731Z-e93f0a/1
  • 환경: macOS, 시스템 Python 3.9.6, bash 3.2, BSD ps. pytest는 지정된 제어 저장소의 .venv/bin/python으로 실행.

기존 3건의 결함은 이번 HEAD에서 해결됐고 수정 전 실패도 확인했다. 다만 새 UUID 대소문자 회귀 테스트 2건이 macOS에서 실패한다. 원인은 제품의 UUID 정규화가 아니라 테스트가 UUID 없는 프로세스를 생성하는 데 있다. 현재 HEAD를 이 Mac에서 통과한 상태로 승인할 수 없으므로 NOT PASS다. 수정 범위는 테스트 픽스처이며 재계획은 필요하지 않다.

수정 필요: [P2] UUID 명령행 보유자 픽스처가 macOS에서 UUID를 잃음

근거: tests/test_issue31_purge_residual.py:315의 subprocess.Popen(["sh", "-c", "sleep 120", conv]), 이어지는 :318의 live 단언. upper와 mixed 두 매개변수에서 동일하게 실패한다.

macOS의 sh는 마지막 명령을 실행하면서 동일 PID의 명령행을 sleep 120으로 바꾼다. conv는 셸의 $0였으며 sleep 인자로 전달되지 않는다. 앞서 live.kill() / live.wait()로 세션 파일의 PID도 죽였기 때문에 이 시점에는 세션 파일이나 명령행 어디에도 살아 있는 보유자가 없다. 어댑터가 False를 돌려주는 것은 이 픽스처 상태에서 올바르다.

재현 절차:

  1. 검증 트리에서 아래 테스트 노드를 실행한다. 임시 HOME/HOME_DIR, CLAUDE_PROJECT_DIR, CLAUDE_CODE_TMPDIR, TMPDIR를 사용하고 브리프의 HERDR/MAM/WORKSPACE 환경 변수들을 제거한다. 테스트의 home 픽스처는 CLAUDE_CONFIG_DIR를 제거하여 임시 HOME/.claude를 쓰며, own_tmp_roots는 고정 /tmp fallback까지 임시 경로로 대체한다.

    PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider --basetemp=<검증트리밖의새임시경로> -q tests/test_issue31_purge_residual.py::test_any_spelling_of_the_uuid_is_the_lowercase_session
    
  2. 독립적으로 새 UUID를 생성하고 Popen(["sh", "-c", "sleep 120", uuid])를 띄운 뒤 0.1초 후 ps -p <그 PID> -o args=를 읽는다. 관측 결과는 sleep 120으로 UUID가 없다. 종료 시 생성한 프로세스만 kill/wait한다.

  3. 검증 트리 HEAD의 git archive 임시 복사본에서 해당 한 줄만 Popen([sys.executable, "-c", "import time; time.sleep(120)", conv])로 대체하고 같은 두 테스트를 실행한다.

기대/실제: 원래 테스트는 lowercase UUID를 명령행에 가진 살아 있는 프로세스를 만들고 upper/mixed ID 모두 in-use=True임을 검증해야 한다. 실제로는 UUID 없는 sleep을 만들어 두 건 모두 :318에서 실패했다. 임시 복사본의 한 줄 대체 대조군에서는 2 passed in 2.36s, 종료 코드 0이었다. 별도 ps 확인에서도 Python 프로세스는 UUID 인자를 유지했다.

수정 방향: 셸의 마지막 명령 최적화에 의존하지 않는 프로세스를 생성한다. 이미 import한 sys.executable을 사용한 위 Python sleep 픽스처가 최소 수정이다. 생성 PID의 명령행에 UUID가 실제로 남아 있는지 확인하고 기존 finally의 kill/wait 정리는 유지한다. 원본 검증 트리나 creator 트리는 수정하지 않았다.

기존 지적의 해결 및 수정 전 실패 검증

현재 테스트 파일을 각 수정 직전 커밋의 격리된 git archive 복사본에 놓고 해당 노드를 실행했다. 테스트 임시 루트 격리 픽스처도 함께 사용했다. 이는 예전 테스트가 아니라 새 회귀 단언이 예전 구현을 실제로 거부하는지 확인하기 위한 것이다.

기존 지적 현재 구현 및 HEAD 결과 수정 전 결과
UUID 아닌 memory가 공유 폴더를 삭제 claude.py:10 정규 UUID 확인/소문자 변환, :301 후보 경로 구성 및 :368, :390 purge/residual 진입 가드. malformed/non-UUID 보존 테스트 통과. upper/mixed의 세션 파일 기반 in-use 및 CLI exit 4 단계도 통과 bd09c6a^에서 test_claude_rejects_malformed_uuid[memory] 실패: 기대 []와 달리 memory 관련 경로 3개 삭제
상위 심볼릭 링크를 통한 범위 밖 삭제 claude.py:105의 _inside가 루트 아래 상위 구성요소를 lstat하고 :336의 _owned_paths가 필터링. tmp key/project key/file-history 링크 뒤 파일 보존, leaf 링크만 삭제, 루트 자체 링크 허용 테스트 통과 6e0361c^에서 test_claude_purge_never_follows_a_linked_dir_below_a_root 실패: 링크 뒤 경로 4개 삭제
NaN/inf/과대 정수 startedAt이 죽음으로 판정 claude.py:69의 _session_file_live: bool/type/finite/positive 검사 및 float 변환 OverflowError 처리로 판정 불가를 live로 유지. 숫자 경계값 및 CLI 테스트 통과 f943b0d^에서 [NaN] 테스트 실패: 살아 있는 PID인데 False. 추가로 60779ba^에서 [10**400] 테스트 실패: OverflowError

각 수정 전 실행은 1 failed였으며, 동일 노드들이 현재 HEAD의 관련 모듈 실행에서는 통과했다. 기존 3건을 계속 미해결인 제품 결함으로 보고하는 것은 아니다.

검증 결과와 범위

다음 네 모듈을 함께 실행: tests/test_issue31_purge_residual.py, tests/test_issue42_skill_blocks.py, tests/test_issue55_agent_homes.py, tests/test_a4_adapter_contract.py.

  • 원본 HEAD: 146 passed, 2 failed, 1 skipped in 37.80s. 위 P2의 upper/mixed 두 건만 실패. 전체 스위트는 실행하지 않았다.
  • 임시 복사본의 픽스처 대조군: 위 두 건만 2 passed. 이를 원본 HEAD 전체 통과로 합산하지 않는다.
  • 변경 Python 파일의 시스템 Python 3.9 구문 검사, 변경 셸 파일의 bash 구문 검사, git diff --check 통과.
  • 종료 전 HEAD/tree 재확인 일치, 검증 트리 git status --short 출력 없음.
  • 실제 Claude 실행/실제 세션 purge·stop·in-use는 수행하지 않았다. destructive 테스트는 임시 홈/프로젝트/config/tmp에서만 실행. issue31 외 모듈에도 실제 /tmp/claude-<uid>에 접근하지 않도록 별도 테스트 프로세스용 가드를 적용했다.

추가 검토 사항

  • BSD ps 시작 시각: LC_ALL=C, TZ=UTC0로 읽어 calendar.timegm과 epoch-ms를 비교한다. 실제 임시 sleep 프로세스의 live 판정, 재사용 PID 모형, 판정 불가, UTC/DST 회귀 테스트를 확인했다. startedAt+2초 비교는 유지됐다.
  • tmp 레이아웃: 실제 경로는 디렉터리 이름/종류 메타데이터만 확인했다. 이 Mac의 /tmp는 /private/tmp로 연결되고 실제 per-uid scratchpad는 workspace key 아래 UUID 디렉터리 구조였다. TMPDIR는 별도의 /var/folders/.../T/로 해석된다. override/base/per-uid/realpath 중복 제거 테스트 통과.
  • 실제 ~/.claude의 projects, file-history, session-env, sessions, jobs 디렉터리 구조는 구현이 가정한 배치와 부합했다. 실제 세션 파일 및 key 내용은 읽지 않았다. 관측된 metadata 수: projects 디렉터리 16개, file-history 19개, session-env 21개, sessions JSON 6개와 key 6개, jobs 디렉터리 2개와 JSON 파일 1개. 이는 구조 확인이지 모든 실제 파일의 소유권을 검증했다는 뜻은 아니다.
  • dd292c1: 삭제 가능한 _owned_paths 대신 전체 _candidates로 has_artifacts가 Claude를 선택하므로, 링크 뒤 잔여물만 있어도 auto purge는 Claude로 선택하여 보존 및 residual을 보고한다. test_purge_cli_reports_a_linked_orphan_it_will_not_remove 통과.
  • 공유 파일/다른 UUID scratchpad/동일 접두사 jobs/다른 세션 metadata 보존 및 실패 후 ownership evidence 유지 검증을 관련 모듈에서 확인했다. key 경로는 보고 시 hex를 마스킹한다.
  • base.py:311, __main__.py:186, stop_session.sh:292의 안내 전달을 검토했다. stdout JSON과 stderr 힌트를 분리하고 stop은 in-use gate의 실패를 exit 4로 처리한다. exit 4 및 purge 시 registry 행 삭제 설명이 stop SKILL에 반영됐다. skill code block 관련 검사 통과.
  • agy에도 in-use gate가 적용되는 점은 공통 어댑터 계약의 의도된 동작이며 이 자체를 결함으로 판정하지 않았다.
  • test_tests_never_reach_the_real_tmp_root 및 자식 Python의 fallback 주입 검증 통과. #55 관련 외부 agy 아티팩트를 결함 근거로 사용하지 않았다.
  • 상위 경로 링크 검사는 검사 시점의 디렉터리 구조에 관한 보장이다. 동시 파일시스템 변경을 포함한 원자적 삭제 보장까지 검증한 것은 아니다.

로컬 증거

임시 증거 루트: /tmp/review-a59e709d-4yb9c6z4 (macOS 실경로 /private/tmp/...).

  • head.log: 원본 HEAD 네 모듈 실행 결과.
  • before-id.log, before-links.log, before-nan.log, before-overflow.log: 수정 전 실패 결과.
  • fixture-control.log: 한 줄 픽스처 대체 대조군의 2건 통과.
  • fixture-control/: HEAD archive의 임시 대조군. 제품 코드 변경 없음.

[VERDICT: NOT PASS]