release(v4.11.0): attach uses the herdr-registered name; sessions start and stop only through the skills (Issue #71) - archive the review reports, bump the lockstep version
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
1 parent
5f6065344f
commit
342a9a4edf
24 files changed
+682
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261026T133440Z-c8961b",
|
||||
"base_sha": "29cc4fd90478936f15387a7665e2c95d13a3f14c",
|
||||
"head_sha": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"tree_sha": "98d57bdc60920238c115ece90c90cc694f2744bc",
|
||||
"review_commit": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "f39ae4ba",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-26T17:32:11Z"
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
# 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 5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26, tree 98d57bdc60920238c115ece90c90cc694f2744bc
|
||||
- `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]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261026T133440Z-c8961b",
|
||||
"base_sha": "29cc4fd90478936f15387a7665e2c95d13a3f14c",
|
||||
"head_sha": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"tree_sha": "98d57bdc60920238c115ece90c90cc694f2744bc",
|
||||
"review_commit": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"reviewer": "planner-reviewer-codex-01",
|
||||
"job_id": "eb60f37d",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-26T17:32:11Z"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Review report — PR #73 / Issue #71
|
||||
|
||||
Reviewed validation snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2`: head `5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26`, tree `98d57bdc60920238c115ece90c90cc694f2744bc`, base `29cc4fd90478936f15387a7665e2c95d13a3f14c`. The snapshot was clean; `git diff --check` reported no whitespace errors. No source files were changed.
|
||||
|
||||
## Findings
|
||||
|
||||
No blocking or non-blocking defects found in this pass.
|
||||
|
||||
The earlier rowless-stop finding about `HERDR_SESSION_NAME=default` was re-adjudicated in job `f40aa3bd` and withdrawn. That environment value is treated as unset by create: `create_session.sh:179-187,251-256` chooses the workspace server unless the caller supplies `--herdr-session`. Rowless stop follows the same resolution (`stop_session.sh:109-130`; `lib.sh:1681-1712`). The prior probe put an agent on `default` without the explicit option, a state that invocation of create would not produce. Deliberate default-server selection remains possible with `--herdr-session default` and the stop command accepts the same explicit option.
|
||||
|
||||
## Review checks
|
||||
|
||||
- `attach_command` is centralized in `lib_py/agents/sanitize.py` and used by create (`create_session.sh:512,566`), resume (`update_yaml_resumed.sh:119,151,161`), and reconcile (`reconcile.sh:569,676`). Coverage checks default server rendering, shell quoting, normalized names, and attach argument forwarding.
|
||||
- Runtime references to removed `start_command` and `kill_command` fields are gone; only migration compatibility docs/code and assertions remain. The shim’s `new-session` and `kill-session` routes are still needed by the lifecycle scripts. Cleanup guidance now directs to the stop skill (`create_session.sh:364`, `lib_py/agents/id_order.py:342-345`).
|
||||
- Rowless stop validates strict registry readability (`stop_session.sh:169-173`), refuses purge without a row (`124-126`), resolves cwd/root canonically before checking containment (`145-154`), and leaves registry bytes untouched (`156-164`). The safety and exit-code cases are covered by the new tests and the stop skill table (`SKILL.md:58-67`).
|
||||
- The bash sanitizer is defined once (`lib.sh:44-82`), uses verified hash fallbacks, and errors when no conversion/hash tool is available. On macOS Bash 3.2, names containing `İ`, `-n`, and a trailing newline matched Python under `LC_ALL=C` and `LC_ALL=C.UTF-8`.
|
||||
- Registry migration updates all named rows and format metadata (`registry_format.py:35-66`) before caller mutation under `BEGIN IMMEDIATE` (`atomic_yaml.py:78-88`); stale YAML is repaired from DB state (`127-138`). `atomic_dump_yaml` is the only sanctioned registry YAML writer. Future versions are not downgraded. The migration entry point’s output/error contract is documented and implemented in `registry_format.py:17-25,69-88`.
|
||||
- StallWatch computes the normalized herdr name once and passes it to its source (`stall_watch.py:229`); its unchanged test passes.
|
||||
- S11’s baseline `0fc162e` exists and is an ancestor of this head. Python 3.9.6 and Bash 3.2.57 are the host versions used. The scope excludes Issue #72’s update skill.
|
||||
|
||||
## Verification
|
||||
|
||||
Commands used the required `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp` outside the validation checkout.
|
||||
|
||||
- Head combined review run (all requested focused and adjacent suites): **461 passed, 1 skipped** in 1295.97s. This included `test_issue71_herdr_names.py`, `test_issue66_stall_watch_sanitized_name.py`, tier 1/2/3, Issues 23/24, 50, 21, 63, 60, layout, and skill blocks.
|
||||
- Base Issue #71 regression run from the temporary base snapshot: **68 failed, 2 passed** in 121.85s, matching the expected base behavior.
|
||||
- Unicode/name parity probe under C and C.UTF-8: outputs matched Python.
|
||||
- `git diff --check`: clean.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261026T133440Z-c8961b",
|
||||
"base_sha": "29cc4fd90478936f15387a7665e2c95d13a3f14c",
|
||||
"head_sha": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"tree_sha": "98d57bdc60920238c115ece90c90cc694f2744bc",
|
||||
"review_commit": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"reviewer": "reviewer-agy-01",
|
||||
"job_id": "0a1bb7e7",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-26T17:32:11Z"
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
# PR #73 (`fix/issue-71`) Review Report — `agy` (`herdr:reviewer-agy-01`)
|
||||
|
||||
- **Job ID**: `0a1bb7e7`
|
||||
- **Loop ID**: `loop-20261026T133440Z-c8961b` (iteration 2)
|
||||
- **Issue / PR**: Issue #71 / PR #73 (`fix/issue-71`, 14 commits)
|
||||
- **Base Commit**: `29cc4fd90478936f15387a7665e2c95d13a3f14c` (`main`, `v4.10.1`)
|
||||
- **Head Commit**: `5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26` (tree `98d57bdc60920238c115ece90c90cc694f2744bc`)
|
||||
- **Review Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2`
|
||||
- **Host Environment**: macOS Darwin arm64 (`Darwin 25.3.0`), `/bin/bash 3.2.57(1)-release`, `/usr/bin/python3 3.9.6`
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
PR #73 resolves Issue #71 across all 10 scope items and review refinements (`A`–`F`):
|
||||
1. Unifies `attach_command` construction in [`lib_py/agents/sanitize.py::attach_command`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/agents/sanitize.py#L34-L42) (`herdr --session <server> agent attach <sanitized_name>`) across `create_session.sh`, `update_yaml_resumed.sh` (both branches), and `reconcile.sh`, and teaches the `lib.sh` shim's `herdr agent attach` branch to resolve its target via `_resolve_herdr_target`.
|
||||
2. Removes `kill_command` and `start_command` from newly written rows, `status.sh --json`, and `SKILL.md` examples, and replaces direct `herdr kill-session` cleanup hints in `create_session.sh` (exit 43) and `id_order.py` with `stop_session.sh --session <name> --herdr-session <srv>`.
|
||||
3. Adds row-less session closing (`stop_rowless`) to [`stop_session.sh`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh#L123-L165) with strict registry readability gating (`load_state_json strict`), workspace boundary enforcement (`os.path.realpath` prefix-safe check, exit 5 on foreign workspace), server selection parity with `create_session.sh`, and registry immutability.
|
||||
4. Consolidates `_sanitize_herdr_agent_name` into a single definition in [`lib.sh`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib.sh#L40-L85) injected into the generated shim via `declare -f`, achieving exact parity with Python's `sanitize_herdr_agent_name` across ASCII, non-ASCII/multibyte, option-like (`-n`, `-e`), and newline-containing names, while failing closed (nonzero, stderr) when no hash tool or `python3` is available instead of emitting `00000000` or truncated names.
|
||||
5. Introduces registry format version 2 (`registry_format: 2`) in [`lib_py/registry_format.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/registry_format.py#L1-L92) and [`lib_py/atomic_yaml.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/atomic_yaml.py#L121-L138), migrating under the writer lock (`BEGIN IMMEDIATE`) and repairing stale YAML copies on retry.
|
||||
6. Computes `herdr_name` once in `StallWatch.__init__` and passes it as a required keyword-only argument `name` to `HerdrSource`.
|
||||
|
||||
All 10 review checklist items, macOS `/bin/bash 3.2.57` and `/usr/bin/python3 3.9.6` compatibility checks, negative regression on `base` (`68 failed, 2 passed`), and 461 functional/integration tests on `head` (`461 passed, 1 skipped, 0 failed`) pass on macOS Darwin arm64.
|
||||
|
||||
---
|
||||
|
||||
## 2. Checklist Verification (Items 1–10)
|
||||
|
||||
### 1. Completion Criteria & Scope Discipline (Issue #71 & Scope Comment Items 1–10)
|
||||
- **Verified (`PASS`)**:
|
||||
- Every requirement in Issue #71 and the "범위 정리" comment (items 1–10) is implemented and covered by regression tests.
|
||||
- Out-of-scope Issue #72 (`multi-agent-mux-update` skill) is not pulled into this PR; only the `python3 -m lib_py.registry_format --yaml <path>` entry point specified in item 9 is provided for #72 to call. `MAM_VERSION` (`4.10.1`) and `VERSIONS.md` are untouched as intended.
|
||||
|
||||
### 2. Regression Suite `tests/test_issue71_herdr_names.py` (Base vs Head & Reconcile Pane Precondition)
|
||||
- **Verified (`PASS`)**:
|
||||
- **Base (`29cc4fd90478936f15387a7665e2c95d13a3f14c`)**: Checked out into an isolated temporary directory outside the review worktree and ran `tests/test_issue71_herdr_names.py` (70 tests): **68 failed, 2 passed in 104.17s** (the only 2 passing tests on `base` are `test_the_names_need_converting_and_the_plain_one_does_not` and `test_wrapper_attach_calls_herdr_with_the_herdr_name[lab-plain-creator-claude]`, matching the PR description).
|
||||
- **Head (`5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26`)**: All **70 passed** (including all 4 `hashers` parameterizations: `shasum`, `sha1sum`, `openssl`, `python3`).
|
||||
- **Reconcile test precondition**: [`test_reconcile_registers_with_the_attach_command`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/tests/test_issue71_herdr_names.py#L128-L141) seeds `mock_herdr` with `agents={name: {"agent": "claude", "status": "running", "cwd": str(mam_sandbox), "workspace_id": "w1", "pid": 9999, "pane_id": "w1:p71", "command": "claude", "buffer": "Ready"}}` and asserts `len(rows) == 1` and `rows[0]["last_visible_note"] == "auto-registered by monitor"` before checking `_assert_new_form(rows[0], rows[0]["herdr_session"])`.
|
||||
|
||||
### 3. `attach_command` (`lib_py/agents/sanitize.py`) & `lib.sh` Wrapper `agent attach`
|
||||
- **Verified (`PASS`)**:
|
||||
- [`lib_py/agents/sanitize.py:34-42`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/agents/sanitize.py#L34-L42) defines:
|
||||
```python
|
||||
def attach_command(server, name):
|
||||
return "herdr --session %s agent attach %s" % (
|
||||
shlex.quote(server or "default"), shlex.quote(sanitize_herdr_agent_name(name)))
|
||||
```
|
||||
- Always writes `--session default` when `server` is `"default"` or empty/falsy, and applies `shlex.quote` to both the server and sanitized agent name.
|
||||
- Used by all three writers:
|
||||
- `create`: [`create_session.sh:512, 566`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-create/scripts/create_session.sh#L512-L566)
|
||||
- `resume` (new row and existing row): [`update_yaml_resumed.sh:119, 150, 160`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh#L119-L160)
|
||||
- `reconcile`: [`reconcile.sh:569, 676`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh#L569-L676)
|
||||
- In [`lib.sh:605-613`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib.sh#L605-L613), the shim's `agent attach` branch shifts `<target>` (`t="$1"; shift`), resolves `tgt=$(_resolve_herdr_target "$t" || true)`, fails with exit 1 if empty, and invokes `_real_herdr agent attach "$tgt" "$@"`, preserving trailing flags such as `--takeover`.
|
||||
|
||||
### 4. Removal of `kill_command` & `start_command` and Updated Cleanup Hints
|
||||
- **Verified (`PASS`)**:
|
||||
- `git grep -E 'kill_command|start_command'` across the repository confirms no production code, skill doc, or active schema assertion reads or writes `kill_command` or `start_command` (the only remaining mentions are in `registry_format.py` to drop them during v1 -> v2 migration and in `test_issue71_herdr_names.py` / `test_tier2_component.py` asserting their absence).
|
||||
- [`status.sh:221-231`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-status/scripts/status.sh#L221-L231) and [`multi-agent-mux-status/SKILL.md:94-101`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-status/SKILL.md#L94-L101) no longer include `start_command`, and nothing in the codebase reads `start_command` from `status.sh --json`.
|
||||
- The shim's `new-session` and `kill-session` translation branches in `lib.sh` are properly retained because `create_session.sh`, `resume_session.sh`, `stop_session.sh`, and `reconcile.sh` call them internally.
|
||||
- Both user-facing cleanup hints now point to `stop_session.sh`:
|
||||
- [`create_session.sh:364`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-create/scripts/create_session.sh#L364): `bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh --session $SESSION_NAME --herdr-session ${HERDR_SESSION_NAME:-default}`
|
||||
- [`lib_py/agents/id_order.py:359-360`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/agents/id_order.py#L359-L360): `bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh --session {m['session']} --herdr-session {srv}`
|
||||
|
||||
### 5. Row-less `stop` (`stop_session.sh`) & Strict Registry Read (`load_state_json strict`)
|
||||
- **Verified (`PASS`)**:
|
||||
- **Strict registry read gate**: [`stop_session.sh:170-193`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh#L170-L193) calls `STATE_JSON=$(load_state_json strict)` first. In [`lib.sh:1597-1631`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib.sh#L1597-L1631), `load_state_json strict` re-raises `sqlite3.OperationalError` on missing `state` or `sessions` tables as well as corrupt DB / malformed YAML, printing to `stderr` and exiting `1`. Furthermore, `MAPPED_DATA` exits `7` **only** when `d.get('herdr_sessions', [])` was validly iterated and had no matching dict row; non-dict rows (`herdr_sessions: [- 42]`) exit `1` (`cannot read the row`).
|
||||
- **Workspace boundary check**: [`stop_rowless` (`stop_session.sh:145-155`)](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh#L145-L155) resolves both `PANE_CWD` and `WORKSPACE_ROOT` via `os.path.realpath` and checks `cwd == root or cwd.startswith(root + os.sep)`. Verified on macOS that this prevents prefix confusion (`/tmp/ws` vs `/tmp/ws-other`), handles symlinks (`/var` -> `/private/var`), and normalizes trailing slashes; out-of-workspace sessions are refused with exit code `5` before any `kill-session`.
|
||||
- **Server selection order**: Matches `create_session.sh:184-188`: `--herdr-session` (`HERDR_SERVER_OPT`) -> ambient `HERDR_SESSION_NAME` (when non-empty and `!= "default"`) -> workspace server via `resolve_herdr_session`.
|
||||
- **`--purge-conversation` and exit codes**: Refuses `--purge-conversation` on a row-less session with exit `1`, exits `6` if `herdr has-session` still succeeds after `kill-session`, never mutates `agent-sessions.yaml` or `agent-sessions.db`, and matches [`multi-agent-mux-stop/SKILL.md:59-67`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-stop/SKILL.md#L59-L67).
|
||||
|
||||
### 6. Single Sanitize Rule in `lib.sh` & macOS BSD / Locale Parity
|
||||
- **Verified (`PASS`)**:
|
||||
- [`lib.sh:40-85`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib.sh#L40-L85) has the single definition of `_sanitize_herdr_agent_name`. [`_init_herdr_isolation` (`lib.sh:179-182`)](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib.sh#L179-L182) writes `declare -f _sanitize_herdr_agent_name` into the generated shim (`$wrapper_dir/herdr`).
|
||||
- Removed the duplicate definition in the shim heredoc, removed `"00000000"` and `${s:0:32}` fallbacks, and removed `has-session`'s relative-path `sys.path.insert(0, os.path.abspath(".agents/skills"))` + `lambda s: s.lower()[:32]` fallback (`lib.sh:633-647` now computes `ssess=$(_sanitize_herdr_agent_name "$sess") || exit 1` in bash and passes `TARGET_SAN="$ssess"`).
|
||||
- **macOS `/bin/bash 3.2.57`, BSD `tr`/`sed`, and locale (`LC_ALL=C` and `LC_ALL=en_US.UTF-8`) verification**:
|
||||
- Pure-ASCII check uses `LC_ALL=C tr -d '\000-\177'`. For ASCII inputs, `printf '%s' "$n" | tr '\n' '-' | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g'` avoids `echo` flag parsing (`-n`, `-e`, `-En`) and converts `\n` to `-` before `sed` or command substitution `$(...)` can strip trailing newlines.
|
||||
- For non-ASCII inputs (`İ`, `é`, `ü`, `K`, CJK/Hangul, emoji), it delegates lowercasing and `re.sub(r"[^a-z0-9_-]", "-", ...)` to `python3` so character-by-character replacement and Unicode case folding (`\u0130`.lower() -> `'i\u0307'` -> `'i-'`) match `lib_py.agents.sanitize.sanitize_herdr_agent_name` byte-for-byte, and fails closed (exit 1, stderr) if `python3` is unavailable.
|
||||
|
||||
### 7. Registry Format Version 2 (`lib_py/registry_format.py`, `lib_py/atomic_yaml.py`)
|
||||
- **Verified (`PASS`)**:
|
||||
- Inside [`atomic_dump_yaml_main` (`lib_py/atomic_yaml.py:121-138`)](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/atomic_yaml.py#L121-L138), after acquiring the flock and `BEGIN IMMEDIATE` and loading `state` + `sessions`, if `from_version < registry_format.CURRENT` (`2`), `registry_format.migrate(d)` runs before the caller's mutation and sets `force_yaml = True`.
|
||||
- Migrates all rows in `herdr_sessions` (`running`, `stopped`, `terminated`, `archived`), drops `kill_command` and `start_command`, recomputes `attach_command`, and sets `d["registry_format"] = 2`.
|
||||
- If the DB was already at format 2 (`elif row:`) but an earlier YAML `os.replace` failed after `conn.commit()`, `force_yaml = registry_format.stored_version(y) < registry_format.CURRENT` detects the stale YAML and rewrites it on the next write.
|
||||
- **Future format preservation**: Verified that when `registry_format` > 2 (e.g., `3`), `stored_version(d)` returns `3`, `from_version < CURRENT` is `False`, and `force_yaml` is `False` — the writer neither downgrades `registry_format` nor clobbers future fields.
|
||||
- **Other registry paths**: Checked all references to `agent-sessions.yaml` across `.agents/skills/`; `atomic_dump_yaml` is the sole writer in production (`create_session.sh`, `update_yaml_resumed.sh`, `stop_session.sh`, `reconcile.sh`, `orc_onboard.sh`), while `load_state_json` (`lib.sh` / `lib_py/state.py`) is strictly read-only.
|
||||
- **CLI entry point**: `PYTHONPATH=.agents/skills python3 -m lib_py.registry_format --yaml <path>` prints `registry-format: <from> -> <to>, rows changed: <n>` and exits `0` (or nonzero with `registry-format: error: ...` on stderr when missing/failing), matching its module docstring contract.
|
||||
|
||||
### 8. `stall_watch.py` Single Sanitization Point
|
||||
- **Verified (`PASS`)**:
|
||||
- In [`.agents/skills/lib_py/stall_watch.py:158-163, 229-230`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/lib_py/stall_watch.py#L158-L230), `StallWatch.__init__` computes `self.herdr_name = sanitize_herdr_agent_name(args.target)` once and passes `name=self.herdr_name` (required keyword-only parameter) to `HerdrSource`.
|
||||
- `tests/test_issue66_stall_watch_sanitized_name.py` is unmodified in `git diff` and all 7 of its tests pass.
|
||||
|
||||
### 9. Test Updates, `skill_code_blocks.json`, and `test_s11` Anchor (`0fc162e`)
|
||||
- **Verified (`PASS`)**:
|
||||
- `tests/test_issue50_pane_lock.py`, `tests/test_issue21_worktree_prereqs.py`, `tests/test_tier1_unit.py`, `tests/test_tier2_component.py`, and `tests/test_tier3_integration.py` preserve their original test intent after replacing assertions on removed `start_command`/`kill_command` fields.
|
||||
- `tests/data/skill_code_blocks.json` and `tests/test_issue42_skill_blocks.py` pass all 96 skill-block verification tests.
|
||||
- In [`tests/test_issue23_24_startup_path.py:306-335`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/tests/test_issue23_24_startup_path.py#L306-L335), `test_s11_this_branch_does_not_change_the_generated_shim` pins `base = os.environ.get("MAM_BASE_SHA", "0fc162e")` and extracts both `_sanitize_herdr_agent_name() { ... }` and the `cat <<'EOF'` heredoc body in `_heredoc_body`. Commit `0fc162e` is an ancestor of `HEAD` (`5f60653`) on `fix/issue-71`; when merged into `main` via merge commit (as documented in PR #73), `0fc162e` remains reachable in git history so `test_s11` actively verifies the shim rather than skipping. (The `if show.returncode != 0: pytest.skip(...)` guard is pre-existing for shallow/archive checkouts without history.)
|
||||
|
||||
### 10. macOS Compatibility (`/usr/bin/python3 3.9.6`, `/bin/bash 3.2.57`, BSD Userland, SQLite WAL)
|
||||
- **Verified (`PASS`)**:
|
||||
- Parsed all 16 modified/added Python files with `/usr/bin/python3` (`3.9.6`) `ast.parse(..., feature_version=(3, 9))` and imported `lib_py.agents.sanitize`, `lib_py.registry_format`, `lib_py.atomic_yaml`, and `lib_py.stall_watch` under system Python 3.9.6.
|
||||
- Checked all 7 modified bash scripts and the generated `.mam/shim/herdr` with `/bin/bash -n` (`GNU bash 3.2.57(1)-release`); no bash 4+ constructs (`declare -A`, `${var,,}`, `mapfile`, `readarray`) are used.
|
||||
- In `tests/test_issue71_herdr_names.py:514-527`, `test_stop_does_not_take_an_unreadable_registry_for_no_row` unlinks `agent-sessions.db*` / `agent-sessions.db-*` before writing corrupt DB/YAML fixtures, properly accounting for macOS SQLite's persistent `-wal`/`-shm` sidecars.
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Execution Summary on macOS
|
||||
|
||||
| Suite / Command | Target | Result |
|
||||
|---|---|---|
|
||||
| `git diff --check 29cc4fd..5f60653` | Review worktree `/2` | Clean (exit 0) |
|
||||
| `/bin/bash -n` on all 7 modified `.sh` scripts | `/bin/bash 3.2.57` | All 7 OK (exit 0) |
|
||||
| `/usr/bin/python3` (`3.9.6`) AST + import + `LC_ALL=C`/`en_US.UTF-8` sanitize & path checks | System Python 3.9.6 + `/bin/bash 3.2.57` | `ALL_LOCALE_AND_MULTIBYTE_PARITY_OK`, `FUTURE_FORMAT_PRESERVED_OK`, `STOP_ROWLESS_PATH_BOUNDARY_CHECKS_OK` (exit 0) |
|
||||
| `pytest tests/test_issue71_herdr_names.py` | **Base** (`29cc4fd` in temp dir) | **68 failed, 2 passed** in 104.17s |
|
||||
| `pytest tests/test_issue71_herdr_names.py tests/test_issue66_stall_watch_sanitized_name.py tests/test_layout.py tests/test_issue42_skill_blocks.py` | **Head** (`5f60653`, worktree `/2`) | **152 passed, 1 skipped** in 150.63s (all 70 in `test_issue71` and all 7 in `test_issue66` passed) |
|
||||
| `pytest tests/test_issue60_linux_fixes.py tests/test_tier1_unit.py tests/test_tier2_component.py tests/test_tier3_integration.py tests/test_issue23_24_startup_path.py tests/test_issue50_pane_lock.py tests/test_issue21_worktree_prereqs.py tests/test_issue63_same_cwd_ids.py` | **Head** (`5f60653`) | **309 passed, 0 failed** |
|
||||
|
||||
---
|
||||
|
||||
## 4. Defects & Observations
|
||||
|
||||
### Blocking Defects
|
||||
- None.
|
||||
|
||||
### Non-Blocking Observations
|
||||
- In [`.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:1021`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job#L1021), the informational message after `send-keys` prints `(연결하려면: HERDR_SESSION_NAME=$HERDR_SESSION_NAME herdr agent attach $sess — lib.sh를 source한 셸에서 실행)`. Because PR #73 updated the `lib.sh` shim's `herdr agent attach` handler to resolve `$sess` via `_resolve_herdr_target`, this command works as stated when `lib.sh` is sourced, though a future cleanup could also print the native `herdr --session ... agent attach ...` form.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261026T133440Z-c8961b",
|
||||
"base_sha": "29cc4fd90478936f15387a7665e2c95d13a3f14c",
|
||||
"head_sha": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"tree_sha": "98d57bdc60920238c115ece90c90cc694f2744bc",
|
||||
"review_commit": "5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "fd90992c",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-26T17:32:11Z"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Review: PR #73 / Issue #71 (macOS, snapshot 2)
|
||||
|
||||
Job fd90992c. Reviewer only. Checks ran on the review snapshot. The source worktree and the control-plane checkout were not edited.
|
||||
|
||||
## Identity
|
||||
|
||||
- Snapshot: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261026T133440Z-c8961b/2`
|
||||
- Loop: `loop-20261026T133440Z-c8961b`
|
||||
- Base: `29cc4fd90478936f15387a7665e2c95d13a3f14c` (main, v4.10.1)
|
||||
- Head: `5f6065344f800c2c5ad6f802e95e9e8ff5cc7b26`
|
||||
- Tree: `98d57bdc60920238c115ece90c90cc694f2744bc`
|
||||
- 14 commits, base is an ancestor, porcelain 0, no `.mam` in the snapshot, `git diff --check` clean
|
||||
- Diff: 28 files, +953/−176
|
||||
- Host: `/usr/bin/python3` 3.9.6, bash 3.2.57(1)-release, sqlite 3.51.0, `LANG=en_US.UTF-8`
|
||||
|
||||
`tests/test_issue66_stall_watch_sanitized_name.py` has an empty diff against `29cc4fd`. Commit `0fc162e` is an ancestor. The diff file list has no `multi-agent-mux-update` skill. `MAM_VERSION` stays `4.10.1`.
|
||||
|
||||
Issue #71, the "범위 정리" comment (items 1–10), and PR #73 were read. The registry entry point is documented for #72 and is not wired to an update skill.
|
||||
|
||||
## What was checked
|
||||
|
||||
### attach_command
|
||||
|
||||
`lib_py/agents/sanitize.py:34` builds `herdr --session <server> agent attach <sanitized name>`. An empty server becomes `default`, and both the server and the name go through `shlex.quote`, so `default` is written as `--session default` and a server that needs quotes is quoted.
|
||||
|
||||
The three writers call it:
|
||||
|
||||
- create, `create_session.sh:566`
|
||||
- resume, `update_yaml_resumed.sh:151` (new row) and `:161` (existing row whose server is set)
|
||||
- reconcile, `reconcile.sh:676`, on the row whose note is `auto-registered by monitor`
|
||||
|
||||
The shim `agent attach` (`lib.sh:605-613`) resolves the target with `_resolve_herdr_target` and runs `_real_herdr agent attach "$tgt" "$@"`. Options after the name stay in `"$@"`.
|
||||
|
||||
### Removed fields and the stop hints
|
||||
|
||||
`kill_command` and `start_command` remain only as fields the migration pops and as assertions that they are absent. `status.sh` no longer emits `start_command`. The shim still has the fake `new-session` and `kill-session` verbs, which create, stop, and reconcile use. `kill-session` sanitizes the name and exits 1 when that fails.
|
||||
|
||||
Create exit 43 (`create_session.sh:364`) and `id_order.py:330` and `:344` tell the user to run `stop_session.sh --session <name> --herdr-session <server>`. The row-less tests below run that script.
|
||||
|
||||
### Row-less stop
|
||||
|
||||
`stop_rowless` (`stop_session.sh:123`) runs only when the row lookup exits 7 (`:189`). A strict read failure exits 1 before that (`:170`). `--purge-conversation` with no row exits 1 (`:124`). A cwd outside this workspace exits 5. A session that survives `kill-session` exits 6. Those codes match `multi-agent-mux-stop/SKILL.md:58-66`.
|
||||
|
||||
Server order: `--herdr-session`, else a non-default `HERDR_SESSION_NAME` captured before resolve (`:109`, `:128`), else the workspace server. `lib.sh` stamps an unset name as `default`, and the row-less path treats that stamp as unset.
|
||||
|
||||
The cwd check realpaths both sides and accepts the workspace itself or a child (`root + os.sep`). That rejects a sibling prefix and a symlink whose target is outside the workspace, and it treats a trailing slash as the same directory. `/tmp` and `/private/tmp` are the same path on this Mac. The row-less tests compare registry bytes before and after and passed.
|
||||
|
||||
`load_state_json strict` (`lib.sh:1594`) is the bash reader. A missing `state` or `sessions` table raises in strict mode and the function exits 1. The argument-free call still prints `{}`.
|
||||
|
||||
### One name rule
|
||||
|
||||
`lib.sh` has one `_sanitize_herdr_agent_name` (`:44`). `00000000` and `lower()[:32]` are absent. The shim receives the function through `declare -f` (`:181`).
|
||||
|
||||
The function was extracted with `sed` and run under `/bin/bash` 3.2 without sourcing `lib.sh`. Against `sanitize_herdr_agent_name` there were 0 mismatches under `LC_ALL=C` and `LC_ALL=en_US.UTF-8` for empty, `Upper-Case`, `-n`, `-e`, a 33-character name, the long branch name, `İstanbul-creator-claude`, a newline, `a\nb`, café, the kelvin sign, Japanese, Korean `한글`, and `A B`. Korean is two characters; the non-ASCII path uses `python3` `str.lower()` and the same regex, so the replacement follows characters, and the result matched Python.
|
||||
|
||||
With `PATH` limited to `tr`, `sed`, `awk`, and `cut`, the long name exits 1 with `no shasum, sha1sum, openssl or python3 to hash it` and prints no name. `한글` exits 1 with `not ASCII`.
|
||||
|
||||
`/usr/bin/python3` is 3.9.6. The diff adds no `mapfile`, `${var,,}`, `declare -A`, `match`, or `tomllib`. A throwaway SQLite database opened with `PRAGMA journal_mode=WAL`, written, and closed still had a `-wal` (0 bytes) and a `-shm` (32768 bytes).
|
||||
|
||||
### Registry format 2
|
||||
|
||||
`atomic_yaml.py:118-137` migrates under the lock, after `BEGIN IMMEDIATE` and before the caller mutation, when `stored_version < 2`. Migration sets `force_yaml`, so YAML is rewritten even if the mutation changes no session. If the DB is already current and the YAML copy is still older, the next write rewrites the YAML. `migrate` (`registry_format.py:50-66`) updates every named row, including stopped and terminated, and sets `registry_format` to 2. A version already greater than 2 is not less than `CURRENT`, so the writer does not call `migrate`.
|
||||
|
||||
The entry point `python -m lib_py.registry_format --yaml <path>` prints `registry-format: <from> -> <to>, rows changed: <n>` and exits 0, or `registry-format: error:` and a nonzero status when neither file exists. The only `yaml.safe_dump` under `.agents/skills` is the writer. Readers (`lib_py/state.py` and the argument-free bash loader) do not migrate.
|
||||
|
||||
### stall_watch and the edited tests
|
||||
|
||||
`stall_watch.py:229` computes `self.herdr_name` once and passes it into `HerdrSource`. The issue #66 test file is unchanged and is in the passing run.
|
||||
|
||||
Edited assertions keep the old intent: pane-lock uses `session_id_source` (create sets it at `create_session.sh:579`) to tell a create row from a monitor row; the worktree test looks for a leaked `WORKSPACE_ROOT=` in `attach_command`; tier 2 checks the new attach string and the absence of the two dropped keys; tier 1 and tier 3 plant a row with no agent so "cannot infer agent" still exits 2. `skill_code_blocks.json` moved with the docs, and `test_issue42_skill_blocks.py` expects stop exits `0` through `6`. That file passed aside from the skills-link skip.
|
||||
|
||||
S11 (`test_issue23_24_startup_path.py:306-310`) compares the shim with `0fc162e`. That commit is in this checkout, so the comparison ran as part of the passing file. If the object is missing, the test calls `pytest.skip` with a message that names the commit. That skip is intentional: the PR says a squash would drop `0fc162e`, and it asks for a merge commit.
|
||||
|
||||
The reconcile tests plant a mock pane (`test_issue71_herdr_names.py:127-140`) and require one new row. They passed, so registration ran.
|
||||
|
||||
## Tests
|
||||
|
||||
Pytest was `PYTHONDONTWRITEBYTECODE=1` with the control-plane venv, `-p no:cacheprovider`, and `--basetemp=/tmp/mamfd9-fd90992c-pytest`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `WORKSPACE_ROOT`, `DELEGATE_JOB_LOGS_DIR`, `PYTHONPATH`, the four `XDG_*` homes, and `AGENT_SESSIONS_YAML` were unset. `AGENT_PYTHON_BIN` pointed at the control-plane venv. The snapshot was the working directory and was not checked out.
|
||||
|
||||
Head, the files named in the brief plus `test_b19_headless_reconcile_fixes.py`, `test_b4_session_created.py`, and `test_issue30_stall_watch.py`: **546 passed, 2 skipped** in 1458.68s, exit 0. `tests/test_issue71_herdr_names.py` collects 70 tests and they are in that pass. The two skips, confirmed with `-rs` on those two tests, are `test_claude_skills_link_resolves_to_agents_skills` (`.claude/skills` absent, `test_issue42_skill_blocks.py:266`) and `test_s6_the_heartbeat_timer_is_not_a_background_child` (Linux `/proc`, `test_issue30_stall_watch.py:340`).
|
||||
|
||||
Base: a `--shared` clone at `/tmp/mamfd9-fd90992c-base`, detached at `29cc4fd`, with only this test file copied in. Same pytest flags, `--basetemp=/tmp/mamfd9-fd90992c-base-pytest`. **68 failed, 2 passed** in 98.42s, exit 1. The two passes are `test_the_names_need_converting_and_the_plain_one_does_not` and the plain-name `test_wrapper_attach_calls_herdr_with_the_herdr_name`. The long and uppercase wrapper cases failed.
|
||||
|
||||
## Residuals (non-blocking)
|
||||
|
||||
- Readers stay read-only. An old registry keeps a format-1 `attach_command` until the next writer, or until `python -m lib_py.registry_format` runs. The module docstring describes that window. The explicit entry point is left for #72.
|
||||
- `VERSIONS.md` is unchanged. The release sentence from the scope comment is in the PR body.
|
||||
- `migrate` skips a row with no `name` (`registry_format.py:57-58`), so that row can keep `kill_command` after the format key becomes 2.
|
||||
- `lib_py/state.py` `load_state_json` still returns `{}` on a read error. Stop uses the bash strict reader.
|
||||
- The SKILL.md exit-0 cell describes a stop or an already-stopped no-op. The pre-flight paragraph and the script header also describe the row-less close as exit 0. The pre-flight sentence names `--herdr-session` and `HERDR_SESSION_NAME`; the code also uses the workspace server when both are absent.
|
||||
- A direct call to `migrate` on a format already greater than 2 would write the key back to 2. The writer does not call it in that case.
|
||||
- S11 skips only when `0fc162e` cannot be resolved. A squash merge would take that path. The PR forbids squash for that reason.
|
||||
|
||||
## Host
|
||||
|
||||
Checked before the suite at 2026-10-27 01:43:34 and again after it at 02:08:54. No process whose first command token was `opencode`. `lsof` of the user opencode database returned exit 1. Database mtime 1792641027, size 36712448. `opencode.log` absent. Config `service.json` mtime 1792246838, size 64. State `service.json` absent. Herdr config mtime 1784456394, size 19. `~/.local/state/mam` directory mtime 1792846843, size 128. `id-order` is a directory, mtime 1792846886, size 128; its entries were not listed. The same two herdr servers were present before and after: pid 11655 (`/opt/homebrew/bin/herdr server`) and pid 69309 (`/opt/homebrew/bin/herdr --session multi-agent-mux server`). No server argv contained the scratch prefix, the snapshot path, or the loop id. The database was not opened.
|
||||
|
||||
After the suite the snapshot was still head `5f60653`, tree `98d57bdc`, porcelain 0, no `.mam`. The control-plane checkout was still `29cc4fd`, porcelain 0.
|
||||
|
||||
Rechecked after this report was written, at 2026-10-27 02:09:37. The snapshot was still head `5f60653`, tree `98d57bdc`, porcelain 0, with no `.mam`. The control plane was still `29cc4fd`, porcelain 0. Opencode first-token count 0, database `lsof` exit 1, and the database, log, service files, herdr config, `~/.local/state/mam`, and `id-order` mtimes and sizes were the same as the pre-suite reading. Herdr servers were still pid 11655 and pid 69309. No server argv contained the scratch prefix, the snapshot path, or the loop id.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user