Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-f39ae4ba.md
T

23 KiB
Raw Blame History

PR #73 (Issue #71) 리뷰 — head 5f60653

  • 리뷰어: planner-reviewer-claude-01 (macOS Darwin 25.6, /bin/bash 3.2.57, /usr/bin/python3 3.9.6, BSD sed/tr)
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2
  • identity 확인 (시작·종료 시 모두):
    • HEAD 5f6065344f, tree 98d57bdc60
    • git status --porcelain --ignored 비어 있음
    • .mam 없음. 추적 파일 .mam.env.example 만 있다.
    • main HEAD 29cc4fd 그대로.
  • 범위: 29cc4fd..5f60653, 커밋 14개, 파일 28개 (+953/−176).
  • 읽은 것: 이슈 #71 본문과 댓글("범위 정리" 1–10항, #72 분리), PR #73 본문.

0. 이번 잡(f39ae4ba)에 대해

  • 이 잡은 같은 루프의 검증 경로 /2 다. head 5f60653, tree 98d57bdc 는 앞 잡 0698ffc5(검증 경로 /1) 와 같다. 브리프의 확인 항목도 잡 ID와 경로를 빼면 글자까지 같다.
  • 그래서 아래 1장의 분석(파일:줄 근거, 리뷰어 시험 p1–p12, 변이 m1–m12, base 비교)은 같은 tree 의 git archive 사본에서 얻은 결과를 그대로 쓴다.
  • 이번 경로 /2 에서 새로 확인한 것:
    • 브리프에 붙은 diff(1920줄)가 git diff 29cc4fd 5f60653 와 바이트까지 같다. 커밋은 14개다.
    • /2 의 lib.sh 에서 _sanitize_herdr_agent_name 을 다시 뽑았다. 정의는 하나이고, 앞서 비교한 정의와 같다.
    • /2 의 lib_py 를 기준으로 bash↔Python 비교를 다시 돌렸다. 408건에 불일치는 0이다. python3 와 해시 도구가 모두 없는 5건도 기대대로 동작했다.
    • 테스트 세 묶음을 /2 에서 다시 돌렸다. 결과는 아래 2장 표와 같다.
      • issue71+issue66: 77 passed
      • A: 384 passed, 1 skipped
      • B: 543 passed, 3 skipped
    • 실제 herdr 서버 두 개(pid 11655, 69309)는 실행 전후로 같다.
    • 시작과 끝에 /2 의 HEAD·tree 를 확인했다. status --ignored 는 비어 있고 .mam 은 없다. main HEAD 는 29cc4fd 그대로다.

1. 확인한 것과 근거

1) 완료 조건과 범위

항목 결과 근거
1 attach 형태 충족 아래 3)
2 default 도 적음 충족 아래 3)
3 래퍼 attach 충족 아래 3)
4 두 필드 삭제 충족 아래 4)
5 원칙(스킬로 시작·종료), 행 없는 stop 충족 아래 4), 5)
6 attach 는 예외 충족 attach 만 herdr 명령을 직접 쓴다
7 변환 규칙 하나 충족 아래 6)
8 해시 불가 시 오류 충족 아래 6)
9 마이그레이션 충족 아래 7)
10 stall_watch 한 곳 충족 아래 8)
  • 보강 완료 조건도 모두 충족한다.
    • 새 행에 두 필드가 없다.
    • 해시 도구가 없으면 python3 로 같은 결과를 내고, python3 도 없으면 오류다.
    • 옛 행의 마이그레이션은 멱등이고, YAML 과 DB 가 같다.
    • 행 없는 stop 은 레지스트리를 바꾸지 않는다.
    • 정리 안내는 stop 스킬 명령이다.
  • #72(update 스킬)는 끌어들이지 않았다.
    • 진입점 python3 -m lib_py.registry_format 만 두었다.
    • 그 진입점을 부르는 코드는 없다.
  • 본문 완료 조건 6(실제 에이전트를 32자 초과 이름으로 띄워 attach)은 수동 확인 항목이다. 브리프 제약(실제 에이전트·실제 herdr 금지)에 따라 하지 않았다.
  • 대신 형태가 맞는지는 확인했다. 실제 herdr 0.9.5 의 agent attach --help 를 격리 HOME 에서 보면 사용법이 herdr agent attach <TARGET> [OPTIONS] 이고, 옵션은 --takeover 다.

2) 회귀 테스트 tests/test_issue71_herdr_names.py (70개)

  • head (검증 경로): issue71 70개와 issue66 7개를 함께 돌려 77 passed.
  • base: 29cc4fd 사본에 head 의 테스트 파일을 넣었다. conftest 는 이 PR 에서 바뀌지 않았다.
    • 결과: 68 failed, 2 passed. PR 이 말한 숫자와 같다.
    • 통과하는 2개:
      • test_the_names_need_converting_and_the_plain_one_does_not: 변환 전제를 확인한다.
      • test_wrapper_attach_calls_herdr_with_the_herdr_name[PLAIN]: 변환이 필요 없는 이름이라 base 에서도 맞다.
  • reconcile 경로 (test_reconcile_registers_with_the_attach_command, test :126-139) 는 행 등록 경로에 실제로 닿는다.
    • mock herdr 에 cwd=mam_sandbox, pane_id 가 있는 agent 를 넣는다.
    • 실행 전에 행이 없음을 확인한다 (:132).
    • 실행 뒤 last_visible_note == "auto-registered by monitor" 인 행이 하나인지 확인한다 (:137-138).
    • 변이 m10 (reconcile 을 옛 형태로) 이 이 테스트 3개를 실패시킨다.

3) attach_command (lib_py/agents/sanitize.py:34-43)

  • 형태: herdr --session <shlex.quote(server or "default")> agent attach <shlex.quote(sanitize(name))>.
    • 서버가 None 이나 빈 값이어도 --session default 가 된다.
    • 변환된 이름은 [a-z0-9_-] 이므로 quote 를 해도 그대로다.
    • 서버 srv x 는 'srv x' 로 감싸진다. 내 시험과 test :51 에서 확인했다.
  • 쓰는 곳은 네 곳이고, 모두 이 함수를 쓴다:
    • create: create_session.sh:566
    • resume 의 새 행: update_yaml_resumed.sh:151
    • resume 의 서버 변경: update_yaml_resumed.sh:161
    • reconcile: reconcile.sh:676
  • 마이그레이션도 같은 함수를 쓴다 (registry_format.py:62).
  • resume 의 else 분기는 서버가 바뀔 때만 attach_command 를 다시 쓴다 (:159-161).
    • 서버가 그대로인 기존 행은 첫 쓰기의 마이그레이션이 이미 새 형태로 바꿔 둔다. 그래서 낡은 값이 남지 않는다.
  • lib.sh 래퍼 (lib.sh:605-613):
    • agent attach <t> ... 의 첫 인자를 _resolve_herdr_target 으로 변환한다. get/read/prompt 와 같은 함수다.
    • 나머지 "$@" 는 그대로 넘긴다.
    • 테스트 :151-159 가 strict mock 에서 ["agent","attach",<herdr 이름>,"--takeover"] 를 확인한다.
    • 변이 m4 (원래 이름을 넘김) 는 LONG·UPPER 에서 잡힌다.

4) kill_command·start_command 삭제

  • 코드·문서에는 이 두 키가 남아 있지 않다. 리뷰 트리 전체를 grep 했다.
    • 남은 곳: 마이그레이션 상수 (registry_format.py:37), 테스트의 "옛 행" 픽스처와 부재 단언, 과거 리뷰 보관본 .agents/reports/*.
  • status --json 의 start_command 키는 빠졌다 (status.sh:224-228).
    • 이 키를 읽는 코드나 테스트는 없다.
    • status SKILL.md 의 예시도 같이 고쳤다.
  • 래퍼의 new-session/kill-session 가짜 명령은 남겨 두는 것이 맞다. 아직 쓰는 곳:
    • create (create_session.sh:266-276, :346)
    • stop (stop_session.sh:157, :307, :321)
    • reconcile
  • 사용자에게 보이던 kill-session 안내 두 곳은 stop 스킬 명령으로 바뀌었다:
    • create exit 43 (create_session.sh:364)
    • id_order (id_order.py:343-345)
    • 바뀐 명령 stop_session.sh --session X --herdr-session S 는 행이 없어도 동작한다 (아래 5).
    • exit 43 은 _mark_row_rolled_back "running" "rollback-failed" 로 행을 남기므로, 그 경우는 행 있는 stop 경로를 탄다.

5) 행이 없는 stop (stop_session.sh:117-191)

  • 행이 없을 때만 그 경로로 간다.
    • 행을 찾는 Python 은 행이 없을 때만 sys.exit(7) 을 낸다.
    • [ "${MAPPED_RC:-0}" -ne 7 ] || stop_rowless (:189) 로 7 일 때만 row-less 로 간다.
    • 다른 실패는 cannot read the row 로 exit 1 이다 (:190-193).
    • 변이 m6 (0 이 아니면 모두 row-less) 은 test_stop_does_not_take_an_unreadable_row_for_no_row 에 잡힌다.
  • 레지스트리를 읽지 못할 때는 load_state_json strict (:170, lib.sh:1598-1631) 가 exit 1 로 끝난다.
    • 해당 경우: YAML 깨짐(DB 없음), DB 손상, sessions 테이블 없음, 테이블 전부 없음.
    • 테스트 :514-541 의 4개 매개변수가 이를 확인한다.
    • 인자 없는 기존 호출은 동작이 같다. strict 일 때만 raise 하거나 exit 한다.
  • 작업 공간 판정 (:142-151) 은 realpath 두 개를 비교한다: cwd == root or cwd.startswith(root + os.sep). 내 추가 시험 (head 사본, tests/test_zz_review71.py) 결과:
경우 결과
p1 접두어 혼동 (<ws>-other) exit 5, 닫지 않음
p2 밖에서 작업 공간 안으로 가는 심볼릭 링크 0, 닫음
p3 작업 공간 안에서 밖으로 가는 심볼릭 링크 exit 5
p4 후행 슬래시 0
p5 WORKSPACE_ROOT 를 심볼릭 링크로 지정 0
p7 32자 초과 이름 (strict mock) 0
  • 서버 결정 순서 (:107-113, :128-130) 는 --herdr-session → 환경의 HERDR_SESSION_NAME → 작업 공간 서버다.
    • 환경 값이 비었거나 default 이면 작업 공간 서버로 간다. create 의 :185, :253 과 같은 규칙이다.
    • p6 (환경 값 default) 에서 모든 agent get 이 작업 공간 서버로 간 것을 확인했다.
    • 테스트 :487-499 는 환경 서버를 확인한다.
  • 레지스트리를 바꾸지 않는다. 모든 row-less 테스트가 YAML/DB 해시를 비교한다 (_registry_bytes).
  • --purge-conversation 은 행이 없으면 exit 1 이다 (:124-126, test :544-550).
  • 종료 코드:
    • 0 / 1 / 5 / 6 은 SKILL.md 표 (:58-67) 와 헤더 주석 (:30-36), 코드 (:134, :154, :161, :163) 가 같다.
    • test_literal_exit_rule_on_base_scripts 는 {0..6} 이다.
    • skill_code_blocks 의 entry_points 도 5·6 을 더했다.

6) 이름 변환 (lib.sh:44-82)

  • 정의는 하나다 (lib.sh:44). test :171 이 확인한다.
  • shim 은 { printf shebang; declare -f _sanitize_herdr_agent_name; cat <<'EOF' … } > "$tmp_file" (:179-1393) 으로 같은 정의를 받는다.
    • bash 3.2 의 declare -f 출력을 다시 source 한 사본도 아래 비교에 넣었다.
    • 이 호스트의 bash 는 /bin/bash 3.2 하나뿐이라, 테스트의 shim 도 3.2 로 돈다.
  • has-session 의 lambda s: s.lower()[:32] 대체와 os.path.abspath(".agents/skills") import 는 없어졌다.
    • 대신 lib.sh 가 계산한 TARGET_SAN 을 넘긴다 (:633-641).
  • macOS 실측 비교: 이름 34개를 Python sanitize_herdr_agent_name 과 비교했다.
    • 이름: ASCII, 대문자, 32/33/40/200자, 숫자·_·-n·-e·--help 로 시작, \n(가운데·앞·뒤·연속), \t, \r, *?[a], \, %s, Ü, İstanbul…, 日本語, 이모지, K(U+212A), ß, Dž, Ω… 긴 이름, NBSP 등.
    • 원본 정의와 declare -f 사본 2 벌 × 환경 6 개 (LANG=en_US.UTF-8, LC_ALL=C, 로캘 없음, LC_ALL=UTF-8+LC_CTYPE=C, 해시 도구 없이 python3 만 + UTF-8 또는 C) = 408 건, 불일치 0.
    • ASCII 가 아닌 이름은 바이트 단위 치환을 쓰지 않고 python3 의 문자 단위 str.lower()+치환을 쓰므로 Python 과 같다 (:51-56).
  • 해시 도구별: /sbin/sha1sum 만, openssl(LibreSSL) 만, 깨진 shasum(빈 출력)에서 openssl 로 넘어가는 경우 모두 Python 과 같았다 (mam-wt-issue-71-very-lo-54e536a7).
  • python3 도 해시 도구도 없을 때:
    • 짧은 ASCII 이름(대문자, 끝의 \n 포함)은 정상이다.
    • 32자 초과는 no shasum, sha1sum, openssl or python3 오류로 rc 1 이다.
    • ASCII 가 아닌 이름은 not ASCII and there is no python3 오류로 rc 1 이다.
    • 00000000 은 어디에도 없다.
  • 호출하는 곳은 실패를 넘긴다: || return 1, || exit 1, wait_for_agent_idle 은 || return 3, delegate-job 은 진단만 건너뛴다.
    • spawn 의 agent_name 계산은 루프 앞 (:688) 으로 옮겨졌다. 그래서 이름을 못 구하면 spawn 하지 않는다. test :267 이 확인한다.

7) 레지스트리 형식 2 (registry_format.py, atomic_yaml.py:118-137, :192)

  • 순서: 잠금 → BEGIN IMMEDIATE (:79-87) → DB 또는 YAML 읽기 → 마이그레이션 (:121-126) → 호출자 변경 (:144-149) → 검증 → DB 쓰기 → commit → YAML 쓰기.
    • 마이그레이션은 잠금 안에서, 호출자 변경 전에 돈다.
  • 모든 행을 바꾼다. migrate 는 이름이 있는 모든 행에서 두 키를 지우고 attach_command 를 다시 만든다.
    • stopped·terminated 도 포함한다. 변이 m8 (running 만) 은 6 failed 로 잡힌다.
  • YAML 도 반드시 다시 쓴다. force_yaml = True (:126) 이고, 변이 m2 는 6 failed 로 잡힌다.
  • 멱등이다. test :327-338 이 확인하고, 진입점을 두 번 부르면 2 -> 2, rows changed: 0 에 바이트도 같다 (test :342-362).
  • YAML 교체가 실패한 뒤의 복구: DB 가 2 이고 YAML 이 2 미만이면 다음 쓰기에서 force_yaml 이 된다 (:127-137).
    • 진입점으로 하든 아무 쓰기로 하든 복구된다 (test :379-411).
    • 변이 m3 은 2 failed 로 잡힌다.
  • 다른 쓰기 경로: 레지스트리 YAML/DB 를 쓰는 코드는 atomic_yaml 하나뿐이다.
    • grep 결과 safe_dump, INTO state/sessions 는 이 파일에만 있다. hermes.py 의 DELETE 는 에이전트 자신의 DB 다.
    • orc_onboard 와 autopin 도 atomic_dump_yaml(_main) 을 거친다.
    • 호출자 변경 가운데 d 를 통째로 새로 만드는 것은 없다. 만들어도 다음 쓰기가 다시 마이그레이션하므로 형식 키를 잃지 않는다.
  • 읽기 경로: load_state_json 과 lib_py.state 는 바꾸지 않았다. strict 인자만 더했다.
  • 미래 형식 (p8): registry_format: 3 이면 진입점은 3 -> 3, rows changed: 0 이다. 쓰기 뒤에도 키는 3 으로 남고, 행(옛 키 포함)도 그대로다. 내려 쓰지 않는다.
  • 비정상 값 (p9): "2", True, None 은 1 로 보고 2 로 바꾼다. 0 은 0 -> 2 로 출력된다.
  • 진입점 계약:
    • 성공은 stdout 한 줄 registry-format: <from> -> <to>, rows changed: <n> 에 exit 0 이다.
    • 레지스트리가 없으면 exit 1 이다 (test :423).
    • DB 손상 (p10) 은 stderr registry-format: error: file is not a database 에 exit 1 이고 stdout 은 비어 있다.
    • 검증 거부 (p11) 는 registry-format: error: VALIDATE: … bad status 에 exit 1 이다.
    • 모두 docstring 과 같다.
  • macOS persistent WAL: YAML/DB 바이트 비교 테스트(진입점 재실행, row-less stop)가 이 호스트에서 모두 통과했다.

8) stall_watch

  • StallWatch.__init__ 이 self.herdr_name 을 한 번 구한다 (:229).
  • 그 값을 HerdrSource(..., name=self.herdr_name) (:230) 와 wait_once (:332) 에 같이 쓴다.
  • HerdrSource 는 keyword-only name 이 필수다. 원래 이름으로 대신 쓰는 경로는 없다.
  • 다른 호출자는 test_issue60_linux_fixes.py:99 하나이고, name="t" 를 넘기도록 바뀌었다.
  • tests/test_issue66_stall_watch_sanitized_name.py 는 diff 가 없고, 7 passed 다.

9) 바뀐 기존 테스트

  • test_tier2_component: HERDR_SESSION_NAME= 포함 여부 대신 새 attach 형태 전체가 같은지와 두 키가 없는지를 본다.
    • 서버 전파와 라벨이 새지 않는지를 보는 원래 의도는 유지되고, 단언은 더 엄격해졌다.
  • test_issue50_pane_lock:173: create 가 쓴 행이라는 증거를 start_command 대신 session_id_source 로 본다.
    • reconcile 의 행 (reconcile.sh:666-680) 에는 이 키가 없다. 그래서 증거로서 같은 구분을 한다.
  • test_issue21_worktree_prereqs:584: 제어 루트 경로가 새지 않는지를 attach_command 에서 본다. 의도가 같다.
  • test_tier1_unit / test_tier3_integration:
    • "agent 를 추론할 수 없으면 exit 2" 를 이제 행이 있는 세션으로 확인한다. 행이 없으면 row-less 경로로 가기 때문이다.
    • 의도가 유지된다.
  • test_issue63, test_issue23_24 S9: 안내 문구를 새 명령으로 바꿨다.
  • test_layout, test_issue42: heredoc 시작 위치와 종료 코드 집합을 따라 고쳤다.
  • tests/data/skill_code_blocks.json:
    • 블록 33개 가운데 sha 가 바뀐 것은 문서 내용이 바뀐 두 블록이다: create :134 (YAML 행 예시), status :74 (JSON 예시).
    • 나머지는 줄 이동과 evidence 줄 번호다.
    • entry_points 의 stop 종료 코드에 5·6 을 더했다.
    • 문서 diff 와 맞다.
  • S11 (test_issue23_24_startup_path.py:294-316):
    • 비교 기준을 0fc162e 로 두었다. 0fc162e..HEAD 사이 lib.sh 변경은 5f60653 의 load_state_json 하나뿐이고, shim heredoc 과 sanitize 함수는 바뀌지 않았다.
    • merge commit 으로 병합하면 0fc162e 가 main 이력에 남으므로 계속 맞다.
    • 기준을 찾지 못하면 pytest.skip 한다. 이는 base(525df2b) 때부터의 동작이다. git 이 없는 설치본이나 squash 병합에서 건너뛰는 것은 기존 설계 그대로라고 본다.
    • 검증 경로(git worktree)에서는 skip 이 아니라 통과했다 (-k s11: 1 passed).
    • squash 하면 이 확인이 사라지므로 PR 본문의 "merge commit 필수" 를 지켜야 한다.

10) macOS 호환

  • 바뀐 Python 6개 파일이 /usr/bin/python3 3.9.6 에서 compile 된다.
  • attach_command 와 migrate 가 3.9 에서 실행된다.
  • 바뀐 셸 줄에 declare -A, mapfile, ${x,,}, sed -i, stat -c, readlink -f, local -n 이 없다.
  • 이름 변환은 BSD tr/sed 와 bash 3.2 에서 위 6) 처럼 확인했다.

2. 실행한 테스트

  • 실행 조건:
    • 모두 env -i PATH=/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin, 임시 HOME/XDG_DATA_HOME 에서 돌렸다.
    • PYTHONDONTWRITEBYTECODE=1 -p no:cacheprovider --basetemp=<스크래치> 를 썼다.
    • .mam.env 는 export 하지 않았다.
  • 실제 herdr 서버 두 개는 테스트 전후로 같다: pid 11655 herdr server, 69309 herdr --session multi-agent-mux server, 시작 시각도 같다.
대상 결과
검증 경로: test_issue71_herdr_names + test_issue66_stall_watch_sanitized_name 77 passed (/1, /2 모두)
base 29cc4fd 사본 + head 의 issue71 테스트 68 failed, 2 passed
검증 경로 A: tier1_unit, tier2_component, tier3_integration, issue23_24_startup_path, issue50_pane_lock, issue21_worktree_prereqs, issue63_same_cwd_ids, issue60_linux_fixes, layout, issue42_skill_blocks 384 passed, 1 skipped (/1, /2 모두; skip: issue42 의 .claude/skills is untracked; absent in a worktree, 환경 요인)
검증 경로 B (stop·reconcile·create·resume 인접): b4_session_created, b19_headless_reconcile_fixes, c1_tui_readiness, issue15_registry_isolation, issue17_workspace_root_isolation, issue31_purge_residual, issue24_resume_dialogs, issue40_auto_pin, issue45_restore, issue6_fail_closed, issue9_server_env, issue64_ro_connect, uuid_target, workspace_scope, issue57_logs_follow_registry, issue30_stall_watch, issue30_stall_watch_stop, issue50_dispatch_herdr, issue60_opencode_v2, issue36_prompt_transport, a4_adapter_contract, issue26_ints 543 passed, 3 skipped (/1, /2 모두)
S11 단독 (검증 경로) 1 passed (skip 아님)
리뷰어 추가 시험 (head 사본, p1–p12) 14 passed. 1개는 내 기대값 오류다 (형식 0 → "0 -> 2" 출력, 동작은 정상)
bash↔Python 이름 변환 비교 408 건 불일치 0, 도구 없음 5 건 모두 기대대로 (/2 의 lib.sh·lib_py 로 다시 실행)

변이 (head 사본, issue71 70개):

# 변이 결과
m1 작업 공간 판정을 startswith(root) 로 (접두어 혼동) 살아남음 (리뷰어 p1 은 잡음)
m2 마이그레이션 뒤 YAML 강제 쓰기 끔 6 failed
m3 YAML 복구 분기 끔 2 failed
m4 래퍼 attach 에 원래 이름 2 failed
m5 줄바꿈 → - 제거 6 failed
m6 rc≠0 이면 모두 row-less 1 failed
m7 strict 에서 state 테이블 오류만 무시 살아남음 (아래 N3)
m8 running 행만 마이그레이션 6 failed
m9 서버 없으면 --session '' 3 failed
m10 reconcile 을 옛 attach 형태로 3 failed
m11 행 없는 stop 이 환경 서버를 무시 1 failed
m12 해시 실패 시 00000000 4 failed

3. 차단 결함

없음.

4. 비차단

N1: 행 없는 stop 이 제어 루트 밖 워크트리의 세션은 닫지 못한다

  • 상황:
    • worktree_prepare.sh:229-236 은 creator 를 WORKSPACE_ROOT=$MAIN 과 --workspace $WT 로 만든다.
    • $WT 는 --path 로 받은 임의의 경로이고, 보통 제어 루트 밖이다. 루프의 mam-wt-* 나 $TMPDIR/mam-review/… 가 그 예다.
  • 결과:
    • 그런 create 가 행을 등록하기 전에 죽으면, id_order 의 안내 (id_order.py:343-345) 는 stop 명령을 준다.
    • 그 stop 은 realpath(cwd) 가 WORKSPACE_ROOT 밖이므로 exit 5 로 거부한다 (stop_session.sh:151-154). 다른 작업 공간 테스트 (test :467) 와 같은 경로다.
    • 거부는 안전하다(아무것도 바꾸지 않음). 하지만 이 경우에는 안내된 스킬 명령으로 정리할 수 없다.
  • 권고: 둘 중 하나.
    • (a) row-less 판정에 "이 제어 루트에 묶인 워크트리(git worktree list)" 도 넣는다.
    • (b) exit 5 메시지에 그 경우의 정리 방법을 적는다.

N2: 접두어 혼동을 고정하는 테스트가 없다

  • m1 이 PR 테스트에서 살아남는다. <ws>-other 를 cwd 로 하는 한 줄짜리 시험을 더하기를 권한다. 내 p1 이 그 형태다.

N3: strict 읽기에서 state 테이블만 없는 DB 는 걸리지 않는다

  • m7 이 살아남은 이유는 테스트의 no-tables 가 sessions 테이블 오류로도 잡히기 때문이다.
  • state 만 없는 DB 에서도 stop 은 sessions 테이블로 행을 정확히 찾으므로 영향은 없다.

N4: 이 PR 이 건드리지 않은 attach 안내가 남아 있다 (모두 base 부터 있던 것)

  • create_session.sh:770: Attach: herdr session attach $SESSION_NAME. 서버를 적지 않고, 이름도 변환하지 않는다. 행의 attach_command 를 출력하기를 권한다.
  • multi-agent-mux-delegate-job:1021: HERDR_SESSION_NAME=… herdr agent attach $sess (lib.sh를 source한 셸에서). 래퍼가 이제 이름을 변환하므로 동작은 하지만, 새 형태와 다르다.
  • deploy/install_mam.sh:330: HERDR_SERVER_NAME=… herdr agent attach. 없는 환경 변수 이름이다.

N5: 래퍼 attach 는 대상이 첫 인자라고 가정한다

  • herdr agent attach --takeover <name> 순서로 쓰면 래퍼는 --takeover 를 대상으로 찾다가 exit 1 을 낸다.
  • herdr 의 사용법(<TARGET> [OPTIONS])과 문서의 순서대로라면 문제가 없다.

N6: YAML 이 아직 없는 작업 공간에서는 행 없는 stop 이 동작하지 않는다

  • 처음 만든 작업 공간에서 첫 create 가 행 등록 전에 죽어 YAML 이 아직 없으면, stop 은 행 없는 경로 전에 YAML not found 로 exit 1 한다 (:101).
  • 드문 경우다.

N7: 정리할 만한 작은 것들

  • create_session.sh:519 의 server_opt 는 쓰는 곳이 없다. base 에서도 그랬다.
  • shim 내용이 바뀌었으므로, 같은 제어 루트를 쓰는 옛 버전 체크아웃과 새 버전 체크아웃이 섞이면 .mam/shim/herdr 를 서로 덮어쓴다. #45 때와 같은 전환기 비용이다.

[VERDICT: PASS]