Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-ae9c1f28.md
T
GodopuandClaude Opus 5.5 ffe8665b27 release(v4.9.0): Issue #50 / #55 - archive the review reports and bump the lockstep version
Merge review on macOS (--review-only loops, --all-reviewer): round 1 at
320aa9f 4/4 NOT PASS, round 2 codex F1 (a claimed dry-run job holds its
session), round 3 4/4 PASS (discarded: the worktree changed mid-review),
round 4 at 21f0c12 claude/agy/grok PASS with codex interrupted by a stream
disconnect, round 5 codex PASS. Full suite at 21f0c12 on macOS / Python
3.9 / bash 3.2 / herdr 0.9.3: 1860 passed / 0 failed / 38 skipped; no new
entries in ~/.claude/projects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-12 21:38:50 +09:00

7.6 KiB

PR #56 4차 재리뷰 — 수정 커밋 21f0c12 (job ae9c1f28)

  • 리뷰어: planner-reviewer-claude-01 (Reviewer)
  • 리뷰 identity: loop-20261012T013821Z-656dfe / base efd27def .. head 21f0c12b (커밋 1개, 2 파일) / tree c2357ac0
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1 — HEAD·tree 가 identity 와 일치, git status --porcelain 0 줄(시작·종료 시). 본 체크아웃 HEAD 42e5173(main) 변동 없음.
  • 실행: 워크트리 venv python 3.9.6, env -i 로 HERDR_/MAM_/MQTT_*/WORKSPACE_ROOT/CLAUDE_PROJECT_DIR/HOME_DIR/CODEX_HOME 을 비우고 호스트의 긴 TMPDIR 은 그대로. .mam.env 는 읽지 않았다. 범위는 이 커밋만.

1. 전제 확인 — 'default' 스캔은 정말 HERDR_SESSION_NAME 의 서버를 보는가 (확인할 것 1)

코드. reconcile 의 스캔은 subprocess.run(['herdr', ...]) 로 PATH 의 herdr 를 부른다(reconcile.sh:505-510). lib.sh 를 source 하면 $WORKSPACE_ROOT/.mam/shim 이 PATH 맨 앞에 들어가고(lib.sh:144-147), create_session.sh 는 lib.sh 를 source 한 채 reconcile.sh --once 를 자식으로 띄우므로(create_session.sh:748) 그 reconcile 의 herdr 는 shim 이고 HERDR_SESSION_NAME(= create 의 서버, 워크스페이스 슬러그)을 물려받는다. shim 은 -L X 를 HERDR_SESSION_NAME=X 로 바꾸고(lib.sh:184-190), 실제 바이너리를 부를 때 HERDR_SESSION_NAME 이 비어 있지 않고 default 가 아니면 --session <그 값> 을 앞에 붙인다(lib.sh:270-283; ls 경로의 python 도 MAM_LS_SESSION 으로 같은 값을 씀, :1256-1318). 따라서 -L 없는 'default' 스캔 = herdr --session <HERDR_SESSION_NAME> ls 이다. 전제 성립.

실서버(herdr 0.9.3, 일회용 HOME /tmp/ae9c-h*, 일회용 서버 이름).

  • lib.sh 를 source 한 셸에서 HERDR_SESSION_NAME=t-ae9c-srv 로 shim herdr ls -F …(-L 없음) → rc 0. 그 HOME 아래 생긴 서버는 .config/herdr/sessions/t-ae9c-srv/ 하나뿐이고 default 소켓 .config/herdr/herdr.sock 은 생기지 않았다 → 'default' 스캔이 환경변수의 서버로 갔다. -L t-ae9c-srv ls 도 rc 0(같은 서버). 서버는 --session t-ae9c-srv server stop 으로 내리고 HOME 제거, 잔류 프로세스 0.
  • 참고로 원 바이너리는 HERDR_SESSION_NAME 을 모른다: /opt/homebrew/bin/herdr status server 는 HERDR_SESSION_NAME=t-ae9c-x 에서 default 소켓(…/herdr.sock)을, HERDR_SESSION=t-ae9c-y 와 --session t-ae9c-z 에서 sessions/<이름>/herdr.sock 을 가리켰다. 즉 전제는 shim 을 거칠 때만 성립하는데, reconcile 은 위와 같이 shim 을 거친다(그리고 lib.sh 를 source 한 셸에서는 herdr 자체가 함수다). 수정이 "HERDR_SESSION_NAME 이 비어 있지 않고 default 가 아닐 때" 로 조건을 건 것은 _real_herdr 의 조건(:271-274)과 정확히 같은 경계다.

2. 변경 자체 (reconcile.sh:659-666)

  • drift B 에서만, srv == 'default' 이고 ambient 가 유효할 때 srv 를 ambient 로 바꾼다. 이후의 server_opt, herdr_session/herdr_server, start_command/attach_command/kill_command 가 모두 그 srv 를 쓰므로 행이 일관되게 "답한 서버" 로 적힌다. srv 는 루프 머리(:607)에서 매 반복 t['server'] 로 다시 읽히므로 다음 항목으로 새지 않는다. 스캔(:505)·drift A(:594-595)·pane_meta(name, srv)(:650, 스캔한 라벨로 호출 — 같은 ambient 로 답하므로 동일 서버) 는 그대로다.
  • bash 변경 없음(bash -n 통과), python 3.9 호환.
  • tests/data/skill_code_blocks.json: create_session.sh:550 이 실제로 raise SystemExit(4) 임을 확인(sed -n 550p), test_issue42_skill_blocks 통과.

3. 부작용 검토 (확인할 것 2)

  • 다음 reconcile 에서 drift A 로 오판되지 않는가. 행의 서버가 X(ambient)로 적히면 다음 reconcile 은 그 행에서 X 를 unique_servers 에 넣고(:499-502) -L X 로 스캔하므로(:505-507) alive_set 에 (name, X) 가 들어가고(:581), drift A 의 비교 (name, X)(:594-595)가 맞는다 — 데몬의 환경(HERDR_SESSION_NAME 유무·다른 값)과 무관하다. 같은 create 환경에서는 'default' 스캔도 X 로 답해 (name,'default') 까지 더해지지만 비교에 쓰이지 않을 뿐 해가 없다. scenario4 가 바로 이 경우다: 같은 폴더 6 개 create 가 각자 reconcile --once 를 띄워 서로의 pane 을 보며, 6 행이 모두 살아남아야 한다 → 통과(아래).
  • 기존 'default' 행·HERDR_SESSION_NAME 없는 데몬. 조건이 ambient 가 비어 있거나 'default' 면 발동하지 않으므로(:665) 종전과 같은 'default' 라벨이 적힌다. 이미 'default' 로 적힌 행의 drift A 판정은 이 커밋이 건드리지 않았다.
  • 같은 pane 이 'default' 스캔과 X 스캔에 두 번 보일 때. herdr_sessions 에는 (name,'default')·(name,X) 두 항목이 들어오지만 drift B 는 이름 기준으로 건너뛴다: any(_sanitize(y) == name for y in yaml_session_names)(:610) 와 등록 직후의 yaml_session_names.add(name)(:715). 어느 항목이 먼저 오든(정렬된 서버 순) 첫 항목이 X 라벨로 등록되고 두 번째는 건너뛴다 → 중복 없음. 두 경로 모두 라벨은 X 로 같다(-L X 는 srv=X, 'default' 는 ambient 로 교정).
  • HERDR_SERVER_NAME 만 있는 경우. unique_servers 는 그 값을 넣지만(:497-498) 라벨 교정은 HERDR_SESSION_NAME 만 본다. shim 의 라우팅(_real_herdr)도 HERDR_SESSION_NAME 만 보므로 그때의 'default' 스캔은 실제로 default 서버를 본다 — 라벨 'default' 가 맞다. 일관됨.
  • 비차단 메모(기존 코드): :610 의 (name, srv) in yaml_session_names 는 이름 집합에 튜플을 넣어 보는 비교라 항상 거짓이고, 실제 중복 방지는 뒤의 any(...) 가 한다. 동작에는 영향 없음.

4. 테스트 (확인할 것 3)

항목 결과
head: test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking + test_issue50_pane_lock.py + test_issue42_skill_blocks.py + test_issue9_server_env.py + test_b19_headless_reconcile_fixes.py + test_tier1_unit.py + test_tier2_component.py + test_workspace_scope.py + test_b4_session_created.py 212 passed, 1 skipped (464 s)
head: test_issue50_dispatch_herdr.py (실서버 16 케이스, a3 = 같은 폴더 3 개 동시 create 포함) 16 passed (548 s)
수정 전 대조군: git archive HEAD 복사본에 reconcile.sh 만 efd27de 로 되돌리고 scenario4 실패 — ERROR: parallel-sess-3-creator-claude already running in agent-sessions.yaml, assert 4 == 0 (브리프의 증상 그대로 재현)

5. 잔류물

  • 일회용 HOME /tmp/ae9c-h, /tmp/ae9c-h2, /tmp/ae9c-h3 제거, 일회용 서버 t-ae9c-srv 정지(잔류 프로세스 0). /tmp/mam50-* 박스 0, 실험 herdr 서버 0. 검증 경로에는 .mam 조차 생기지 않았고 git status 깨끗. scratch 복사본·basetemp·프로브 스크립트는 세션 scratchpad 아래. 실제 .mam·모니터·세션·실제 서버·에이전트 홈은 건드리지 않았다(실제 서버에 대해서는 herdr status 읽기 한 번뿐).

결론

'default' 스캔이 shim 을 거쳐 HERDR_SESSION_NAME 의 서버를 본다는 전제는 코드와 실서버로 확인됐고, 행을 "답한 서버" 로 적는 교정은 drift A·중복 등록·기존 행·환경변수 없는 데몬에 부작용이 없다. create 의 자기 서버 행 인수(F2)와 맞물려 scenario4 가 이 Mac 에서 통과하며, 수정 전에는 브리프의 rc 4 로 실패한다.

[VERDICT: PASS]