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
10 KiB
🔍 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클린), basemain 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.exampleFORMAT 절에 한 줄 추가했다. - (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_S0 예산만큼 정확히 대기 MAM_CHILD_WAIT_S0 하위 잡을 한 번만 확인 MAM_JOB_TREE_MAX1 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]