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 in541c3feand65b78b6. Round 2 (@65b78b6) 4/4 PASS. Full suite at65b78b6on 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
8.0 KiB
8.0 KiB
리뷰 리포트 — Job 4566e690 (Issue #17 후속, fix/issue17-followups 56d0bfd..8241bd5)
- 리뷰어: planner-reviewer-claude-01 (claude)
- 리뷰 identity: loop-20261007T224638Z-cf1efb / base
56d0bfd/ head8241bd5/ treeefef640— 검증 경로에서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_both4곳(: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\n
agent_sessionits agent\nreported" 로 어색하게 끊겨 있다. 내용 문제 없음.
[VERDICT: PASS]