- Resolve Herdr shim 5 routing & paste defects (ISSUE-1 ~ ISSUE-5):
* paste-buffer: use pane send-text without auto-enter, propagate rc=3 to send_keys_safe
* exact-match pane resolution: remove substring matching ('in tn') across all branches
* workspace scoping: introduce HERDR_WORKSPACE_ID and .mam/herdr_workspace_id persistence
* unified resolver: single _resolve_herdr_pane_id helper across shim commands
- Resolve dialog token false-positive on 'Yes, try it' tip and isolate fullscreen modal rejection
- Sync mock Herdr CLI contracts in tests/conftest.py
- Add contract tests H-15~H-23 and regression tests D-4~D-7 (412 tests, 100% PASS)
- Add multi-agent loop plans, review reports, and bug report
- Update framework and skill packages to v3.0.1
16 KiB
🔍 교차 코드 리뷰 리포트 — Job e8cee19e
- Job ID:
e8cee19e - 리뷰어:
planner-reviewer-claude-01(Reviewer) - 작성일: 2026-08-27
- 대상 브랜치/기준 커밋:
main@4bbd03b - 리뷰 범위: 워킹트리 누적 변경분 (
git diff) — 4 files, +808 / −92.agents/skills/lib.sh(+289/−…)tests/conftest.pytests/test_herdr_shim_contract.pytests/test_b19_headless_reconcile_fixes.py
- 근거 문서:
bug_report.md(v1.0),.agents/reports/planner-reviewer-claude-01/plan-fae58b93.md
0. 요약 (TL;DR)
계획서(plan-fae58b93.md)에 명시된 F-1 ~ F-4 및 워크스페이스 세션 격리(ISSUE-1/2/3/5) 4개 항목이 모두 코드에 반영되어 있으며, 전체 회귀 테스트가 412 passed (실측 699.09s, exit 0) 로 통과함을 리뷰어 환경에서 재실행하여 직접 확인했다.
린트(구문), 동작성, 유실(회귀) 세 관점 모두에서 머지를 막을 결함(blocker)은 발견되지 않았다. 다만 설계상 의도적으로 남겨진 잔여 스코프 갭 3건을 비차단 후속 과제(Non-blocking) 로 기록한다.
1. 검증 방법
| # | 검증 항목 | 방법 | 결과 |
|---|---|---|---|
| V-1 | 전체 회귀 테스트 | .venv/bin/python -m pytest tests/ -q (리뷰어가 직접 재실행) |
✅ 412 passed in 699.09s, exit 0 |
| V-2 | 셸 구문 린트 | bash -n .agents/skills/lib.sh |
✅ 통과 (오류 없음) |
| V-3 | 생성 shim 구문 린트 | bash -n $WORKSPACE_ROOT/.mam/shim/herdr (H-19 테스트 내장) |
✅ 통과 |
| V-4 | Bash 3.2 호환성 | 로컬 GNU bash 3.2.57 (arm64-apple-darwin25) 에서 local arr=() + "${a[@]+"${a[@]}"}" 패턴 실측 |
✅ set -euo pipefail 하에서 정상 동작 |
| V-5 | 정적 교차 검토 | diff 전량 + 인접 컨텍스트(lib.sh 380 |
✅ 계획서와 구현 일치 |
| V-6 | 호출자 영향 분석 | send_keys_safe 호출부 grep (stop_session.sh:204, delegate-job:527) |
✅ 신규 rc=3 를 모두 비정상으로 처리 |
참고: shellcheck 는 본 환경에 미설치되어 실행하지 못했다. 대신
bash -n2종(원본 + 생성 shim) + Bash 3.2 실측으로 대체했다. 이는 CI 게이트가 아니므로 차단 사유가 아니다.
2. 작업 목표별 반영 확인
✅ F-1 — Yes, try it 토큰 정리 및 회귀 테스트
_MAM_DIALOG_TOKENS에서|Yes, try it제거 확인 (lib.sh:62).handle_startup_dialogs의 Escape 거부 분기(lib.sh:2048)는 그대로 보존 — 실제 fullscreen 업셀 모달은 여전히 Escape 로 거부된다. 즉 "탐지 완화"가 아니라 책임 분리(대화형 산문 오탐 제거 / 실제 모달 처리 유지)로 올바르게 구현되었다.- 회귀 고정:
test_prose_yes_try_it_is_not_a_dialog(산문 → DIALOG_CLOSED),test_mam_dialog_tokens_exclude_yes_try_it(토큰 라인 + Escape 분기 동시 검증),test_fullscreen_modal_is_rejected_not_accepted(Escape 전송·Enter 미전송). - 판정: 반영 완료. 오탐(산문으로 인한
send_keys_saferc=2 데드락)과 정탐(모달 거부)이 양방향으로 고정되었다.
✅ F-2b — _herdr_agent_get_scoped 스코핑 단축경로 보완
- 기존의 스코프 없는 존재 확인(
_real_herdr agent get "$x" >/dev/null 2>&1)이has-session,kill-session,capture-pane,send-keys,paste-buffer,agent6개 분기에서 전부 제거되고_herdr_agent_get_scoped/_resolve_herdr_pane_id로 대체됨. _resolve_herdr_target에 명시적 env 미스 시 raw 폴스루 차단(HERDR_WORKSPACE_ID설정 시return 1)이 추가됨 — 이것이 F-2b 의 핵심이며,agent prompt가 타 워크스페이스 동명 에이전트로 프롬프트를 배달하는 경로를 실제로 닫는다.- 호출부 3곳(
prompt/get/read)이$(... || true)+ 빈 문자열 검사 +exit 1로 실패를 삼키지 않고 상위 전달한다.set -e하에서 명령 치환 실패로 셸이 죽지 않도록|| true가 일관되게 붙어 있다 — 계약(주석에 명시)과 구현이 일치. - 회귀 고정:
test_h21_has_session_agent_get_is_workspace_scoped(w2 → rc=1, w1 → rc=0, unset → 전역 유지),test_h22_agent_prompt_does_not_cross_workspace(out-of-scope 시agent prompt호출 자체가 0건임을 mock call log 로 검증). - 판정: 반영 완료. 특히 H-22 가 "에러만 났는지"가 아니라 부작용(RPC 호출)이 발생하지 않았음을 검증하는 점이 좋다.
✅ F-3 — H-19 테스트 정밀화
test_h19_single_resolver_helper_used_by_all_branches가 단순 문자열 카운트에서 case arm 단위 파싱(_case_arm) 으로 정밀화됨.- 검증 강도: (a) 헬퍼 정의 1회 유일성, (b) 5개 분기 전부 헬퍼 호출, (c) 각 분기에 스코프 없는
agent get … >/dev/null잔존 금지 정규식, (d)agentarm 의$sat/$raw단축경로 제거, (e) 헬퍼 블록 밖_herdr_agent_get_scoped()재정의 0건, (f)bash -n2종. list-panesarm 을elsewhere에서 제외한 처리도 타당하다(해당 arm 은 정상적으로 pane list 를 직접 다룬다).- 판정: 반영 완료. "헬퍼는 만들었지만 옛 경로가 살아있다"는 회귀를 구조적으로 차단한다.
✅ F-4 — capture-pane 폴백 복원
capture-panearm 이pane read <pane_id>→agent read <sat>→agent read <sess>3단 폴백으로 복원됨. pane_id 해석 실패 시에도 기존 agent-level 경로가 살아있어 유실 없음.- 동일 패턴이
send-keys(pane 실패 시 agent 이름 폴백),kill-session(pane close + kill-session 2단)에도 유지된다. - 판정: 반영 완료. F-2b 의 엄격화로 인한 기능 유실 위험이 이 폴백으로 상쇄된다.
✅ ISSUE-1 — 이중 제출(double-submit) 차단
paste-buffer가 존재하지 않는agent send대신pane send-text삽입 전용으로 교체되었고, Enter/C-m 을 일절 보내지 않는다. 제출 책임은send_keys_safe가 단독 소유.- pane 미해석 / send-text 실패 시
|| true로 삼키지 않고exit 1→send_keys_safe가rc=3반환. 빈 프롬프트 제출 시나리오가 닫혔다. - 고속 경로(
agent prompt, 원자적 텍스트+제출)와 폴백 경로(send-text → C-m)가 상호 배타적이므로 제출은 정확히 1회. - 회귀 고정:
test_h15_paste_buffer_inserts_without_enter(send-text 정확히 1건, 제출 계열 호출 0건),test_h16_send_keys_safe_submits_exactly_once(Enter/C-m 정확히 1건),test_d5 / test_send_keys_safe_returns_3_when_paste_buffer_fails(rc=3 + 버퍼 정리 수행 + C-m 미전송),test_paste_buffer_branch_never_submits(소스 레벨 토큰 금지). - 판정: 반영 완료. 특히 실패 시에도
delete-buffer를 먼저 수행한 뒤 rc 를 반환하는 순서가 정확하다(버퍼 누수 없음).
✅ ISSUE-2 — substring 오라우팅 제거
_resolve_herdr_target의(not name and agent and agent in tn),has-session의(not an and a.get("agent") and a.get("agent") in tn)두 substring 분기가 모두 제거되고 exact match only 로 대체._resolve_herdr_pane_id의 pane list 매칭도label→name→agent순 정확 일치(원본/sanitized 양쪽 후보)만 수행.- 회귀 고정:
test_h17_no_substring_cross_pane_routing(reviewer-creator-grok-01이agent: "grok"패인에 매칭되지 않음 + 동종 에이전트 2개 중 정확한 패인으로만 send-keys),test_no_substring_matching_remains_in_lib_sh(소스 레벨\bin tn\b잔존 0건). - 판정: 반영 완료.
✅ ISSUE-3 — 워크스페이스 세션 격리
- 3단 스코프 소스:
HERDR_WORKSPACE_ID(env, 하드) →$WORKSPACE_ROOT/.mam/herdr_workspace_id(persist) → 없으면 기존 서버 전역 조회. - cwd 추론을 하지 않는다는 결정이 주석에 명시되어 있고 구현도 일치 — 정당한 교차 워크스페이스 조회를 조용히 막지 않는다.
pane split에--env HERDR_WORKSPACE_ID="$existing_ws"주입,workspace create경로에서는 응답에서workspace_id를 파싱해export+ 파일 영속화.pane list --workspace서버측 필터 + 파이썬 클라이언트측 재필터(구버전 herdr 가--workspace를 무시할 경우 대비) 이중화 — 방어적으로 잘 설계됨.- 회귀 고정:
test_h18_workspace_scoped_pane_resolution(w1/w2 동명 라벨 분리 라우팅, unset 시 전역 유지),test_h23_persisted_workspace_id_scopes_without_env(env 없이 파일만으로 스코핑 + new-session 이 파일을 실제로 기록). - 판정: 반영 완료. 잔여 갭은 §4 참조.
✅ ISSUE-5 — 파서 중복 제거
agent get → pane_id를 파싱하던 인라인 python heredoc 3벌(kill-session,send-keys, 구capture-pane)이 전부 제거되고_resolve_herdr_pane_id단일 진입점으로 수렴.- pane_id 정규식이 계획서 §1.2 의 지적대로
^w[A-Za-z0-9]+:p[A-Za-z0-9]+$로 채택됨 — 버그 리포트의^w[0-9]+:p[0-9]+$를 그대로 썼다면w1E:p1형태의 실제 pane_id 를 전량 거부했을 것이다. 이 수정은 정확하며,test_h20_pane_id_regex_accepts_alphanumeric_workspace로 accept/reject 5케이스가 고정되어 있다. - 판정: 반영 완료. 리뷰 과정에서 가장 위험했던 함정을 계획 단계에서 잡아낸 점이 확인된다.
3. 테스트 하네스(conftest.py) 변경 검토
mock herdr 변경이 "테스트를 통과시키기 위한 눈속임"이 아니라 실제 herdr CLI 계약에 더 가깝게 교정하는 방향인지를 중점 확인했다.
| 변경 | 평가 |
|---|---|
agent send 서브커맨드 제거 + 미지원 서브커맨드 exit 1 |
✅ 정확한 교정. 실제 herdr agent --help 에 send 가 없다. 기존 mock 이 존재하지 않는 명령을 성공시켜 ISSUE-1 을 은폐하고 있었다. |
agent prompt 가 미매칭 시 exit 1 (기존: 항상 ok) |
✅ 정확한 교정. 무조건 성공하던 mock 이 F-2b 검증을 불가능하게 만들고 있었다. |
pane send-text / pane read / pane rename 추가 |
✅ 신규 코드 경로가 실제로 사용하는 명령이며, agents/panes 양쪽 저장소를 모두 조회하는 구현이 shim 의 폴백 구조와 대칭이다. |
pane list 를 agents ∪ panes 병합 + pane_id 중복 제거 로 변경 |
✅ 필요한 수정. 기존 if not panes_list: 조건부는 agent 가 하나라도 있으면 라벨 전용 패인을 통째로 감췄다 — H-16/H-18/H-23 이 검증하려는 시나리오 자체를 표현할 수 없었다. |
save_state 가 동일 pane_id 를 append 대신 in-place 갱신 |
✅ 버그 수정. 기존 로직은 갱신을 무시(첫 항목 고정)했다. 기존 병렬 상태 경합 테스트(10-agent)도 여전히 통과한다. |
monkeypatch.delenv("HERDR_WORKSPACE_ID") |
✅ 필수. 호스트 환경 오염으로 인한 위양성/위음성 차단. |
유실 검토: agent send 제거는 프로덕션 코드에서 해당 호출이 완전히 사라진 뒤에 이뤄졌으며(grep 결과 잔존 0건), 412 테스트 전량 통과가 이를 뒷받침한다. 기능 유실 없음.
4. 비차단 후속 과제 (Non-blocking / 관찰 사항)
머지를 막지 않으며, 별도 티켓으로 추적할 것을 권고한다.
N-1 (Low) — 영속 파일은 herdr 워크스페이스를 1개만 기억한다
_herdr_persist_ws_id 는 new-session 마다 파일을 덮어쓴다. 레이아웃 오버플로(W2b)로 하나의 MAM 워크스페이스가 herdr 워크스페이스 2개 이상을 소유하게 되면, 파일은 마지막 것만 가리킨다. 이 경우 _herdr_ws_scope 를 쓰는 pane list 폴백은 앞선 워크스페이스의 라벨 전용 패인을 찾지 못할 수 있다.
- 완화 요인:
agent get경로(env-only 스코프)가 먼저 시도되므로 정상 등록된 에이전트는 영향받지 않는다. 영향 범위는 "다중 herdr 워크스페이스 + 라벨 전용 패인" 교집합으로 좁다. - 구현자가 이 트레이드오프를
_herdr_agent_get_scoped주석에 명시적으로 문서화한 점은 적절하다. - 권고: 향후 단일 id 대신 id 목록(append + dedupe)으로 확장.
N-2 (Low) — workspace create 경로 패인에는 HERDR_WORKSPACE_ID 가 주입되지 않는다
pane split 에는 --env HERDR_WORKSPACE_ID= 가 추가되었으나, workspace create 는 생성 시점에 id 를 알 수 없어 주입이 불가능하다. 해당 패인에서 실행되는 에이전트는 env 없이 파일 스코프에만 의존한다.
WORKSPACE_ROOT가 MAM 워크스페이스 단위이므로 일반적인 경우 올바르게 동작한다. 다만 N-1 과 결합하면 스코프가 흔들릴 수 있다.- 권고:
workspace create직후pane set-env(지원 시)로 사후 주입.
N-3 (Low) — _resolve_herdr_target 의 agent-list 폴백이 pane_id 를 반환할 수 있다
print(a.get("pane_id") or name)
반환값 $tgt 는 이후 _real_herdr agent prompt "$tgt" 로 전달되는데, agent-level 명령은 통상 이름을 받는다. pane_id 가 반환되면 그 호출이 실패할 수 있다.
- 본 변경분이 도입한 결함이 아니다 — 변경 전 코드도
print(pane_id or agent)로 동일했다(기존 동작 보존). 또한 그 앞의agent get2단이 성공하는 정상 경로에서는 도달하지 않는다. - 권고:
name을 반환하도록 정리.
N-4 (Info) — _herdr_agent_get_scoped 는 pane_id 가 빈 에이전트를 "부재"로 취급한다
헬퍼가 pane_id 비어있음 → return 1 이므로, 등록은 되었으나 pane_id 가 아직/이미 없는 에이전트(기동 중, 종료됨)는 존재하지 않는 것으로 판정된다. HERDR_WORKSPACE_ID 가 설정된 상태에서는 agent prompt 가 exit 1 로 끝난다.
- 실무상 pane 없는 에이전트에 프롬프트를 넣는 것은 어차피 무의미하므로 현재로선 안전한 방향의 실패(fail-safe) 이다. 동작 변화로 기록만 해 둔다.
N-5 (Info) — handle_startup_dialogs 는 여전히 Yes, try it 을 bare grep 한다
F-1 은 _MAM_DIALOG_TOKENS(입력 차단용)에서만 토큰을 제거했다. handle_startup_dialogs 는 기동 창(기본 20s) 동안 산문에 같은 문자열이 있으면 Escape 를 보낼 수 있다.
- 영향: 기동 직후 유휴 프롬프트에 Escape 1회 → 실질 무해. 또한 이 창은 에이전트가 아직 대화를 시작하기 전이라 산문 노출 확률이 매우 낮다.
- 의도적 설계이며 테스트(
test_fullscreen_modal_is_rejected_not_accepted)로 정탐이 고정되어 있다.
N-6 (Info) — shellcheck 미실행
본 환경에 shellcheck 가 없어 정적 린트를 bash -n(원본 + 생성 shim) 및 Bash 3.2 실측으로 대체했다. CI 게이트가 아니므로 차단하지 않으나, 향후 CI 에 shellcheck 를 추가하면 $env_flags 무인용 확장(SC2086) 등 기존 관용 패턴에 대한 명시적 예외 선언을 함께 정리할 수 있다.
5. 결론
- 계획서
plan-fae58b93.md의 F-1 / F-2b / F-3 / F-4 및 워크스페이스 세션 격리(ISSUE-1/2/3/5) 가 코드에 빠짐없이 반영되었음을 diff 전량 판독으로 확인했다. - 각 수정에 대해 동작 검증형 회귀 테스트(mock RPC 호출 로그 기반)와 소스 레벨 회귀 방지 테스트(옛 패턴 잔존 금지)가 쌍으로 추가되어, 향후 되돌림에 대한 방어가 이중으로 걸려 있다.
- 테스트 하네스 변경은 통과를 위한 완화가 아니라 실제 herdr CLI 계약 쪽으로의 교정이며, 오히려 기존 mock 이 은폐하던 결함(존재하지 않는
agent send의 무조건 성공)을 드러내는 방향이다. - 리뷰어 환경에서
412 passed(exit 0) 를 독립적으로 재현했다. - 잔여 항목(N-1 ~ N-6)은 전부 저위험 관찰 사항이며, 설계 변경이나 재작업 수준의 재계획을 요구하지 않는다. 따라서
[ESCALATE: PLANNER]는 발행하지 않는다.
최종 승인한다.
[VERDICT: PASS]