Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-4566e690.md
T
GodopuandClaude Opus 5.5 cd82ab6e72 release(v4.8.7): Issue #17 follow-ups - archive the review reports and bump the lockstep version
Merge-workspace review loop (--all-reviewer): round 1 (@8241bd5) claude
grok PASS, agy NOT PASS (two static wrapper-literal tests did not follow
_decided/_exit_for), codex NOT PASS (R1: a yes/no decision that could not
run read as "no"); fixed in 541c3fe and 65b78b6. Round 2 (@65b78b6) 4/4
PASS. Full suite at 65b78b6 on macOS / Python 3.9 / herdr 0.9.3: 1730
passed / 0 failed / 56 skipped; 18 of the skips were the uninitialized
nats-docker submodule in the fresh worktree, and with it
tests/test_deploy_freshness.py is 31 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-08 11:32:39 +09:00

8.0 KiB

리뷰 리포트 — Job 4566e690 (Issue #17 후속, fix/issue17-followups 56d0bfd..8241bd5)

  • 리뷰어: planner-reviewer-claude-01 (claude)
  • 리뷰 identity: loop-20261007T224638Z-cf1efb / base 56d0bfd / head 8241bd5 / tree efef640 — 검증 경로에서 git rev-parse HEAD 'HEAD^{tree}' 일치, 미커밋 변경 없음
  • 커밋 6개: 25c9970·f19f940(항목 1), a670ef5(항목 2), d6cb019(항목 3), c90a0af(항목 4), 8241bd5(테스트 환경 고정). 버전·VERSIONS.md 변경 없음(브리프대로).

결론 요약

항목 1~4 가 요구대로 반영됐고, 정상 경로의 종료 코드·출력·레지스트리·이벤트는 변하지 않았다. 추가 테스트 13건은 수정 전 코드에서 실제로 실패한다(255·127·2 등 예측된 코드로). 항목 1 에는 브리프 문구("테스트 전제 수정")를 넘는 제품 코드 변경(herdr_restore.py 의 placeholder 닫기)이 포함되는데, 실제 herdr 0.9.3 에서 그 변경 없이는 테스트도 resume 의 닫기도 성립하지 않음을 확인했으므로 정당한 범위 확장으로 판단한다(§1). 차단 결함 없음. 비차단 관찰 3건(§6).

1. 항목 1 — herdr 0.9.3 과 test_b

  • 브리프의 전제 "닫기 동작은 cwd 를 보지 않는다 → :275 만 두 경로 허용" 은 불완전했다. herdr 0.9.3 은 디렉터리가 사라진 pane 을 프로세스 없는 placeholder(restore_error 설정)로 복원하고 pane process-info 는 pane_not_found 로 rc 1 을 낸다. 실제 서버 대조: 스크래치 사본에 이전 herdr_restore.py 만 넣고 test_b 를 돌리면 close-restored-shells: Command […'pane','process-info','--pane','w2:p1'] returned non-zero exit status 1 로 실패(1 failed, 11.8 s) — 즉 옛 코드는 Y 의 placeholder 에서 예외를 내 resume 의 닫기 단계 전체가 rc 1 로 끝나고(래퍼는 경고만), 아무 pane 도 닫지 못한다.
  • 수정(herdr_restore.py:58-66 _nothing_running): _shell_in_foreground 가 CalledProcessError 를 내면 pane 에 restore_error 가 있고 출력에 pane_not_found 가 있을 때만 "실행 중인 것 없음"으로 보고, 그 외 실패는 그대로 올린다. 두 조건이 모두 herdr 자신이 붙인 표지라 사용자 pane·다른 세션 pane 이 이 경로로 닫힐 수 없다(agent_session 일치 조건 :83 은 그 앞에서 여전히 적용). 단위 테스트 2건이 "restore_error 없음 + pane_not_found", "restore_error 있음 + 다른 오류" 를 모두 예외 유지로 고정한다. fail-safe 방향(닫는 쪽이 아니라 두는 쪽) 유지.
  • _herdr 는 subprocess.run(..., capture_output=True, check=True) 이므로 CalledProcessError.stderr/.output 이 채워진다(:46-49) — 판정에 쓰는 문자열이 실제로 들어온다.
  • test_issue45_restore.py:267·:275-277 주석·단언이 0.9.1($HOME)/0.9.3(원래 경로) 둘 다 허용. 다른 문서의 "$HOME 으로 복원" 주장: SKILL.md 들에는 없음(create:120, resume:63 은 "empty shells" 만 말함), VERSIONS.md 과거 항목은 브리프대로 제외.
  • 실행: 이 Mac(herdr 0.9.3)에서 tests/test_issue45_restore.py 5개 모두 통과, skip 0.

2. 항목 2 — N2 fail-closed

  • 래퍼(:103-127): _decision_failed 는 stderr 에 사유를 적고 exit 1; _decided <var> <sub> … 는 rc≠0 또는 빈 출력이면 실패; _exit_for 는 답이 ^[0-9]+$ 가 아니면 실패. 다섯 exit "$(_decide exit-code …)" 가 _exit_for 로, 네 sub-status 대입과 finalize-begin 이 _decided 로, finalize-end 는 rc 검사(:856-857), cmd_wait 의 wait-rc 는 _decided + 숫자 검사(:1070-1071). 모든 호출이 서브셸 밖(직접 호출)이라 exit 1 이 래퍼를 끝낸다; { …; } 4>&1 그룹도 서브셸이 아니다.
  • 정상 경로 불변: _decided 는 같은 명령의 같은 한 줄 출력을 변수에 넣고, finalize-end 의 "소유자 사라짐 → rc 0·빈 답" 은 전과 같이 FINAL_JOB_STATUS="" 로 남는다(의도된 기존 동작). test_f_the_normal_path_is_unchanged(실제 래퍼, human 잡, 닿지 않는 브로커 → rc 3, 마지막 줄 = 로그 경로, 오류 문구 없음) 통과. 옛·새 비교 테스트(test_c_*) 전부 통과.
  • rc 로만 읽는 호출: subscriber-up 실패(모듈 깨짐) → "subscriber died early" 경로 → _decided sub-status 가 실패 → exit 1(fail-closed, OK). stall-watch 실패 → || return 0 으로 감시만 꺼진 채 진행(:718). 브리프가 "범위에 넣지 않은 것은 이유를 적으라" 했는데 리뷰어에게 그 이유는 보이지 않는다 — 관찰 N1. #30 감시자는 notify-only 라 잡 종결에는 영향이 없어 비차단.
  • 추가 테스트가 수정 전 코드에서 실패하는가: 이전 래퍼(56d0bfd)를 넣은 사본에서 test_f_* 11건 + test_s_no_printed_decision_is_read_raw 모두 실패 — exit-code 는 exit: : numeric argument required 로 255, 인터프리터 부재는 127, finalize-begin 빈 답은 finalize-end argparse --evidence-until: invalid int value: '' 로 2. 세 가지 모두 브리프가 금지한 종료 방식이며 새 코드는 전부 1 로 끝난다.
  • 이식성: /bin/bash -n 통과(3.2), printf -v·[[ =~ ]] 는 3.2 에서 지원. 테스트는 /bin/sh 셸 스크립트 대역과 sys.executable 만 쓴다.

3. 항목 3 — N1 문구

wrapper.py:24-29 가 "이 모듈은 stdlib 만 import 하지만 python -m lib_py.delegation.wrapper 는 패키지 __init__ 을 먼저 실어 paho 가 로드되며, 래퍼의 $PY 는 원래 paho 가 필요하다"로 정정. 실행 경로 변경 없음(diff 상 docstring 만).

4. 항목 4 — N3 주석

lib_int.sh:9-10 에 "PATH 의 python3 를 쓴다(단독 source 라 $PY 없음); 없으면 mam_int/mam_int_knob 이 아무것도 출력하지 않는다(rc 127)" 추가. 코드 불변.

5. 테스트 실행 (검증 경로, 워크트리 .venv Python 3.9.6, paho 있음, env -i 로 HERDR_·MAM_·MQTT_*·WORKSPACE_ROOT 제거)

실행 결과
tests/test_issue45_restore.py, test_issue45_restore_unit.py, test_issue17_item4_decisions.py, test_issue33_budget.py 310 passed / 0 failed / 0 skipped (425 s)
새 테스트 16건, 이전 래퍼·이전 herdr_restore.py 사본 13 failed / 3 passed (실패 = F 11 + 정적 가드 + placeholder 단위)
test_b (실서버), 이전 herdr_restore.py 사본 1 failed (pane_not_found 예외)
격리: 실행 전후 herdr 프로세스는 기존 multi-agent-mux 서버·attach·오케스트레이터 agent wait(pid 만 교체)뿐, /private/tmp/m45* 없음, ~/.config/herdr sessions·config.toml md5 불변. 실제 .mam/jobs 항목 수 불변. 루프백 bind 제약 없음(브로커 테스트 정상 실행).

6. 비차단 관찰

  • N1 stall-watch 판정 실패 시 조용히 감시 생략(래퍼 :718 || return 0): 모듈이 깨지면 stderr 에 아무 말 없이 stall watcher 가 꺼진다. 종결·레지스트리·이벤트에는 영향이 없어 fail-closed 요구 밖이지만, _decide 의 rc 가 1(판정 "감시 안 함")인지 그 밖(실패)인지 구분해 후자는 경고 한 줄을 남기면 진단이 쉬워진다. 브리프가 요구한 "범위 제외 이유" 가 리포트에 있는지 오케스트레이터가 확인하길 권한다.
  • N2 _env 가 상속 WORKSPACE_ROOT 를 비우지 않음(tests/test_issue17_item4_decisions.py:224-230): HERDR_/MAM_/SKS_ 만 걷어낸다. 새 테스트(:659-660, :692)는 WORKSPACE_ROOT 를 명시하지만, 래퍼를 source 하는 기존 _run_both 4곳(:246, :372, :561, :586, PR #51 유래)은 _env(work, …) 만 써서 에이전트 pane 에서 돌리면 그 pane 의 WORKSPACE_ROOT 로 lib.sh 가 초기화된다 — 브리프가 적은 사고(상속된 WORKSPACE_ROOT 로 다른 워크스페이스의 shim 재작성)와 같은 경로다. _env 에서 WORKSPACE_ROOT 를 제거하고 tmp_path 로 고정하면 한 줄로 막힌다. 이 델타가 만든 문제가 아니라 비차단.
  • N3 docstring 줄바꿈(herdr_restore.py:7-8): "A pane keeps the\nagent_session its agent\nreported" 로 어색하게 끊겨 있다. 내용 문제 없음.

[VERDICT: PASS]