Merge review on macOS (--review-only loops, --all-reviewer): round 1 at320aa9f4/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 at21f0c12claude/agy/grok PASS with codex interrupted by a stream disconnect, round 5 codex PASS. Full suite at21f0c12on 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
7.6 KiB
PR #56 4차 재리뷰 — 수정 커밋 21f0c12 (job ae9c1f28)
- 리뷰어: planner-reviewer-claude-01 (Reviewer)
- 리뷰 identity: loop-20261012T013821Z-656dfe / base
efd27def.. head21f0c12b(커밋 1개, 2 파일) / treec2357ac0 - 검증 경로:
/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1— HEAD·tree 가 identity 와 일치,git status --porcelain0 줄(시작·종료 시). 본 체크아웃 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로 shimherdr 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]