Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-76ca3b1b.md
T
GodopuandClaude Opus 5.5 3563a6fc17 release(v4.8.2): bump framework and eight skills to 4.8.2 in lockstep; archive the follow-up loop's reports
PR #41/#43 review follow-ups F1-F6, implemented by creator-claude-01 in a
loop and reviewed by claude, agy, grok and codex: round 1 (@95eabc6)
codex NOT PASS (a failed env load cached as loaded), fixed in 75f364e;
round 2 4/4 PASS. Full suite at 75f364e 1380 passed / 0 failed. Reviewer
reports (round 2 with identity sidecars) and the creator's reports go to
.agents/reports/<session>/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-03 00:27:24 +09:00

10 KiB
Raw Blame History

🔍 Review Report — PR #41·#43 후속 fix/followups-41-43 (job 76ca3b1b)

  • Reviewer: planner-reviewer-claude-01 (role: planner,reviewer) · 2026-10-02
  • 대상: 0051fd6..95eabc6 (8커밋, 27파일 +397/−88, git diff --check 클린), base main 0051fd6.
  • 검증 경로: /var/folders/.../T/mam-review/loop-20261001T230324Z-b511bf/1. identity 확인: HEAD = 95eabc69…d5f4f, HEAD^{tree} = 46399844…7998 — 브리프와 일치.
  • 안전 규칙 준수: 검증 경로·소스 워크트리(multi-agent-mux-wt/followups)·본 체크아웃 모두 수정·커밋·checkout 없음. 되돌린 사본·합성 파일·ps 대역은 scratchpad 에서만. 실제 .mam.env 를 읽거나 출력하지 않았고 export 하지 않았다. 실제 .mam·데몬·세션·herdr 조작 없음, push·merge·PR 상태 변경 없음.

결론 요약

F1–F6 모두 완료 조건을 만족한다. 범위 밖 동작 변경은 없다. 제품 코드를 바꾸는 항목(F2·F3·F4·F5)의 회귀는 제품 파일만 base 로 되돌린 사본에서 47건 실패한다(아래 §7). 테스트만 고치는 항목(F1·F6)은 base 제품 코드에서도 통과하는 것이 정상이며, F1 은 옛 테스트가 날짜에 따라 실패하던 원인을 ps 대역으로 직접 재현했다. 비차단 관찰 3건(T1–T3).

F1 — A8 의 날짜 의존 (tests/test_issue21_acceptance.py:258-300)

  • 제품 코드(loop_lock.sh 의 mam_lstart)는 그대로다. loop_lock.sh 의 diff 는 F4b 의 MAM_LOCK_STEAL_RETRIES 한 줄뿐이다.
  • 테스트는 마커의 lstart 를 " ".join(out.split()) 로 써서 제품의 tr -s ' ' + 앞뒤 공백 제거와 같게 맞췄다.
  • 날짜와 무관하게 고정하는 장치: padded 파라미터가 PATH 앞에 ps 대역을 둔다. 이 대역은 lstart= 질의에만 Thu Oct 1 08:02:03 2026(두 칸)을 돌려주고, 대역이 실제로 쓰였는지도 단언한다.
  • 옛 버그 재현(직접): 같은 대역 아래에서 제품 mam_lstart 는 Thu Oct 1 …(한 칸), 옛 테스트 방식(.strip())은 Thu Oct 1 …(두 칸)을 낸다 → 불일치 → 잠금이 "stale" 로 보여 두 번째 루프가 거부되지 않는다. 즉 옛 A8 이 1~9일에 실패한 원인이 정확히 이것이고, 새 padded 케이스는 매일 그 조건을 돈다.
  • 같은 패턴의 다른 테스트: test_o3_scoped_guard.py:_get_lstart 는 이미 " ".join(...split()) 로 정규화한다. test_o2_race_free_lock.py:get_lstart 는 정규화하지 않지만 어디서도 호출되지 않는 죽은 헬퍼다(T1, 지우거나 정규화 권고).

F2 — 진입점의 한 줄 오류 (mqtt_common.py:129-140 외 7곳)

  • 공통 헬퍼 load_env_or_report() 가 EnvFileError 를 잡아 ERROR: <path>: cannot read (<type>) 한 줄을 출력하고 False 를 돌려준다. 라이브러리 함수는 sys.exit 하지 않는다 — 진입점(publish_event·registry·job_subscriber·cli·bench·heartbeat·remote 의 main)이 return 1 한다. job_subscriber 는 RC_ERROR(=1)로 이름을 썼다.
  • 값·파일 내용은 출력되지 않는다(예외 메시지가 경로와 예외 종류뿐).
  • 회귀 test_a1_…[7 진입점 × permission/utf8] 14건이 base 에서 모두 실패한다.
  • reconcile.sh 의 내장 구독자는 여전히 ensure_env_loaded() 를 직접 부른다(브리프 목록 밖, 문서화된 그대로).

F3 — .mam.env 세부

  • (a) BOM: envfile.py:60-61 이 utf-8-sig 로 연다. 공통 파서라 두 로더 모두 적용된다. 파일 맨 앞의 BOM 만 벗기므로 중간의 U+FEFF 는 그대로다.
  • (b) 중복 키는 첫 값 우선: 동작은 그대로(apply 가 if key not in environ)이고 .mam.env.example FORMAT 절에 한 줄 추가했다.
  • (c) MESSAGING.md:369: "명시한 파일이 없으면 오류" 를 "조용히 건너뜀(주변 탐색은 안 함), 있는데 못 읽으면 EnvFileError" 로 실제 동작에 맞췄다.
  • 회귀 test_a7_* 3건이 base 에서 실패한다.

F4 — 정수 노브

  • (a) 러너(runner.py:627, :630-643, :762-768): _int_knob 이 _positive_int 규칙으로 읽고, 잘못된 값이면 노브당 한 번 경고하고 기본값을 쓴다. MAM_CHILD_WAIT_S=abc 의 ValueError 가 사라졌다.

  • 최솟값의 의미도 맞다.

    노브 최솟값 의미
    MAM_WAIT_GRACE_S 0 예산만큼 정확히 대기
    MAM_CHILD_WAIT_S 0 하위 잡을 한 번만 확인
    MAM_JOB_TREE_MAX 1 0 이면 모든 트리가 "초과"; 대기하지 않으려면 MAM_CHILD_WAIT_S=0

    세 값 모두 표 RUNNER_INT_KNOBS 에 들어갔다.

  • (b) bash 노브: MAM_READY_EMPTY_GIVEUP·SKS_EMPTY_GIVEUP(min 1), SKS_IDLE_RETRIES·SKS_IDLE_RETRY_BACKOFF_S(min 0), MAM_LOCK_STEAL_RETRIES(min 0), MAM_DIFF_MAX_BYTES/LINES(min 1), RECONCILE_POLL_INTERVAL(min 1 — 0 이면 바쁜 루프)가 mam_int_knob 을 거친다.

    • SKS_QUIESCENT_INTERVAL 은 기본값이 소수(0.5)라 그대로 두고 표에 사유를 남겼다 — 브리프 지시대로다.
    • RECONCILE_POLL_INTERVAL 은 저장소 안의 모든 사용처(테스트 60, 문서 15 s)가 정수라 의미가 깨지지 않는다.
    • diff_collect.sh·loop_lock.sh 는 lib.sh 없이 source 되므로 규칙을 lib_int.sh 로 분리했다. lib.sh 도 같은 파일을 source 한다(declare -F 가드로 이중 정의를 막음). deploy/remove.sh 의 폴백 자산 목록에 lib_int.sh 를 추가한 것은 새 파일을 배포 제거 대상에 넣는 필수 동반 변경이다(범위 안). test_issue21_loop_b.py 의 한 줄도 L15 가 diff_collect.sh 를 복사해 쓰므로 필요한 동반 수정이다.
  • (c) loop SKILL.md 의 Job budget 행에 MAM_WAIT_GRACE_S(기본 300, 0 이상, 잘못된 값은 경고 후 300)를 문서화했다.

  • 회귀 test_b1_runner_*·test_b5_* 가 base 에서 실패한다.

F5 — 테스트 스위치 제거 (stall_watch.py, test_issue30_stall_watch.py)

  • MAM_TEST_UNRELIABLE_AGENT 두 줄을 삭제했다.
  • S13 은 -c 진입점에서 어댑터 클래스의 agent_status_reliable 을 False 로 바꾼 뒤 실제 stall_watch.main 을 돈다. 제품에는 분기가 없고, 검증하는 내용(idle 판정 끔, blocked 알림 유지)도 같다.
  • 새 test_s13_the_unreliable_case_needs_no_switch_in_the_product(MAM_TEST_ 문자열 부재)가 base 에서 실패한다.

F6 — 문서·테스트 정밀도

  • delegate-job SKILL.md:53 은 이제 "≤ 60 s 는 herdr wait 한정, 알림 발행 중에 래퍼가 사라지면 그 발행이 끝날 때까지" 라고 적는다 — 코드(_wrapper_gone 이 wait 직후와 _may_prompt 에만 있음)와 일치한다.
  • "대기 중 래퍼 소멸" 테스트는 이제 두 번째 wait 대역 안에서 gone 을 세우고, ticks == [1, 2] 와 마지막 alive 관측이 (2, False) 임을 단언한다. 이름 그대로를 강제한다.
  • 테스트 정밀화라 base 제품 코드에서도 통과하는 것이 맞다(제품 동작은 PR #41 에서 이미 옳았다).

7. 테스트

7-1. 검증 경로에서의 실행

tests/test_issue26_envfile.py tests/test_issue26_ints.py tests/test_issue21_acceptance.py tests/test_issue30_stall_watch.py
tests/test_issue30_stall_watch_stop.py tests/test_tier1_unit.py tests/test_version_consistency.py tests/test_issue5_phase1_delegation.py
(.mam.env 미export, MQTT_* 미설정, followups .venv)
→ 335 passed, 1 skipped in 451.38s   failed 0   (skip = S6, /proc)

브리프 목록 7파일에 test_issue5_phase1_delegation.py(registry CLI 스냅샷·help 캡처)를 더해, 진입점 변경이 CLI 계약을 깨지 않음을 확인했다. 로그에 외부 브로커 흔적 0건.

7-2. 수정 없이는 실패하는가 — 확인함

검증 경로 사본에서 tests/ 밖의 바뀐 파일 전부를 0051fd6 판으로 되돌리고(새 lib_int.sh 는 삭제) 새·변경 테스트를 돌렸다:

47 failed, 88 passed in 110.20s
항목 base 에서 실패한 회귀
F2 test_a1_python_entry_points_report_an_unreadable_file_in_one_line 14건(7 진입점 × 권한/UTF-8)
F3 test_a7_a_leading_bom_…, …_repeated_key_keeps_its_first_value…, …_missing_explicit_file_…
F4 test_b1_runner_wait_grace/child_wait/job_tree_max_*, test_b5_*(표·최솟값·lib.sh 없이 source 되는 두 스크립트·_pane_quiescent)
F5 test_s13_the_unreliable_case_needs_no_switch_in_the_product
F1·F6 테스트만 고친 항목이라 base 제품 코드에서 통과하는 것이 정상. F1 은 §F1 의 ps 대역 실험으로 옛 테스트의 실패 원인을 직접 재현

7-3. 격리

실제 .mam/jobs 는 이 잡 자신의 파일 외 추가 0. 검증 경로 tracked 파일 변화 없음(HEAD 95eabc6), 본 체크아웃 main 0051fd6.

8. 범위

바뀐 파일 27개를 모두 F1–F6 또는 그 필수 동반 변경으로 귀속할 수 있다.

  • lib_int.sh 분리와 그에 따른 deploy/remove.sh·test_issue21_loop_b.py 의 한 줄은 F4b 의 동반 변경이다.
  • #17 항목 4·#39·#40·#42 범위의 변경과 버전 bump 는 없다.

9. 판정

# 비차단 관찰 근거
T1 test_o2_race_free_lock.py:get_lstart 는 정규화하지 않는 호출되지 않는 헬퍼 — 지우거나 같은 정규화로 맞추면 재발 여지가 없다 tests/test_o2_race_free_lock.py:40-43
T2 reconcile.sh 의 내장 구독자는 ensure_env_loaded() 를 직접 불러 읽기 실패 시 여전히 트레이스백 브리프 진입점 목록 밖; 다음 정리 때 load_env_or_report 로
T3 (확인 완료, 조치 불필요) deploy/install.sh 는 트리 전체를 복사하므로(cp -R "$REPO_URL/.", :128) lib_int.sh 도 함께 배포된다. check_assets_present(:98-108)는 존재 확인용 목록이라 lib.sh 만 있어도 되지만, lib.sh 가 lib_int.sh 를 source 하므로 목록에 추가해 두면 부분 설치를 더 일찍 잡는다 deploy/install.sh:98-108

[VERDICT: PASS]