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
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-12 21:38:50 +09:00
1 parent 21f0c12b9e
commit ffe8665b27
21 files changed
+284 -26

No files matched your search

@@ -0,0 +1,42 @@
# 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]
@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261012T123226Z-e64a91",
"base_sha": "efd27def47a4d67684694ccf43a6ebeede01620f",
"head_sha": "21f0c12b9e2195cba8e196d6ccc2469173302e8a",
"tree_sha": "c2357ac01824d099fcbb7e9f7433b6b05f5e86a6",
"review_commit": "21f0c12b9e2195cba8e196d6ccc2469173302e8a",
"reviewer": "planner-reviewer-codex-01",
"job_id": "7ac94ed6",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-12T12:38:13Z"
}
@@ -0,0 +1,59 @@
# Job 7ac94ed6 review
판정: PASS. 지정된 변경 2개 파일에서 차단할 결함을 발견하지 않았다. 이전 잡 4d9c73e5의 임시 산출물을 사용하지 않고 코드 검토, 테스트 실행, 실서버 탐침을 새로 수행했다. 소스 수정·커밋·push는 하지 않았다.
## 검증 identity
- loop: `loop-20261012T123226Z-e64a91`
- base: `efd27def47a4d67684694ccf43a6ebeede01620f`
- head: `21f0c12b9e2195cba8e196d6ccc2469173302e8a`
- tree: `c2357ac01824d099fcbb7e9f7433b6b05f5e86a6`
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T123226Z-e64a91/1`
- 실행 Python: `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-56-fix/.venv/bin/python` (3.9.6); macOS, bash 3.2, herdr 0.9.3.
검증 시작과 종료 시 HEAD/tree가 지정 값과 일치했고 `git status --short`는 비어 있었다. `git diff --check efd27def47a4d67684694ccf43a6ebeede01620f` 및 `/bin/bash -n .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh`가 통과했다.
## 코드 검토 결과
1. **응답한 서버로 라벨을 기록하는 전제가 맞다.** `.agents/skills/lib.sh:185–189`의 `-L` 처리는 `HERDR_SESSION_NAME`을 설정하고, `:270–283`의 `_real_herdr`는 그 이름을 실제 herdr의 `--session`으로 전달한다. `:1256–1274`의 `ls`도 이 경로를 사용한다. `reconcile.sh:505–530`에서 default 스캔은 `-L`을 생략하므로 상속된 이름의 서버가 응답한다. 새 `reconcile.sh:664–667`은 비어 있지 않은 비-default 상속 이름에 한정해 라벨을 보정하고, `:681–687`의 두 서버 필드와 start/attach/kill 명령에 일관되게 반영한다. 실제 일회용 herdr로도 전제를 확인했다.
2. **중복 등록 및 이후 drift A 오판이 관측되지 않았다.** `reconcile.sh:494–502`는 환경과 기존 YAML의 서버를 수집한다. 저장된 명시적 서버는 다음 실행의 환경이 제거되거나 바뀌어도 스캔 대상이다. `:607–611`의 이름 검사와 `:714–715`의 등록 직후 이름 추가가 default/명시적 서버 스캔에서 같은 pane이 두 번 발견될 때 중복을 막는다. 실서버 탐침에서 양쪽 alias가 alive에 존재해도 등록 행과 최초 drift B는 각각 하나였다. 후속 환경 변경 실행에서도 행은 running이고 drift A/B가 없었다.
3. **기존 default 행의 처리 범위는 유지된다.** 수정은 새 drift B 행 생성에 국한된다. pane_meta와 drift A, 기존 행의 처리는 변경하지 않는다. 실서버 탐침에서 기존 default 행은 재라벨링 없이 running으로 남았다. 상속 서버가 없는 일반 default 경로는 기존 scenario3 회귀 테스트로도 확인했다. 이 수정은 기존 default 행의 마이그레이션을 제공하지 않는다.
4. **create의 F2 서버 일치 조건과 맞는다.** `create_session.sh:540–543`은 monitor 행의 서버가 현재 create 서버와 같아야 인수한다. 새 라벨은 이 조건을 만족시키며 다른 서버 행을 인수하도록 조건을 완화하지 않는다. 같은 폴더 6개 병렬 create 사례 및 pane-lock 회귀 테스트가 통과했다.
5. **코드 블록 메타데이터 갱신이 정확하다.** `tests/data/skill_code_blocks.json:620`의 참조는 실제 `create_session.sh:550`의 `raise SystemExit(4)`를 가리키고 관련 검증이 통과했다.
## 새로 수행한 검증
총 **51 passed, 1 skipped**. 모든 실행의 exit code는 0이다.
| 검증 | 결과 | 근거 |
|---|---|---|
| `test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking`, `::test_e2e_scenario3_drift_auto_reconciliation`, `test_issue50_pane_lock.py`, `test_issue42_skill_blocks.py` | 50 passed, 1 skipped, 97.61초 | `/tmp/7ac94ed6-review/tests.log` |
| 새 실제 herdr 서버/환경 변경 탐침 | 1 passed, 10.89초 | `/tmp/7ac94ed6-review/probe.log`, `/tmp/7ac94ed6-review/test_probe.py` |
건너뛴 1건은 `tests/test_issue42_skill_blocks.py:266`: 추적되지 않는 `.claude/skills`가 리뷰 worktree에 없어서 발생한 기존 조건부 skip이다. scenario4는 mock herdr를 사용하는 회귀 테스트이며, 6개 병렬 create의 성공과 고유 행을 검증한다. 이를 실제 herdr 병렬 create 검증으로 간주하지 않았다.
재현 launcher: `/tmp/7ac94ed6-review/run.py`. 지정 Python으로 다음 인자를 전달한다.
```text
/tmp/7ac94ed6-review/run.py tests/test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking tests/test_tier4_e2e.py::test_e2e_scenario3_drift_auto_reconciliation tests/test_issue50_pane_lock.py tests/test_issue42_skill_blocks.py
/tmp/7ac94ed6-review/run.py probe
```
launcher는 검증 cwd를 고정하고, 상속 HERDR_*/MAM_*/MQTT_*/SKS_* 및 지정 경로 환경변수를 제거한다. 일회용 HOME, 검증 트리 밖 basetemp, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 지정 `AGENT_PYTHON_BIN`을 사용한다. 일반 테스트 root는 `/tmp/7ac94ed6-u75tye4e`, 탐침 root는 `/tmp/7ac94ed6-2ppzeojn`이었다.
### 실제 herdr 탐침 절차와 기대/실제 결과
일회용 HOME과 서버 `review7-a88dd0c7`에서 fake Claude pane `t50-creator-claude`를 실제 herdr로 시작하고, 빈 임시 YAML을 준비했다. 실제 운영 서버나 사용자 에이전트 홈, `.mam.env`, 운영 모니터 데몬은 사용하지 않았다.
1. 상속 `HERDR_SESSION_NAME`으로 실행한 무옵션 `herdr ls`와 명시적 `-L review7-a88dd0c7` 조회가 같은 pane을 반환해야 한다. **실제: 두 출력 일치.**
2. 실제 `reconcile.sh --once --emit-diff` 실행 시 default와 명시적 서버 스캔이 같은 pane을 보더라도 하나의 행만 등록하고 서버 필드·제어 명령은 실제 이름을 사용해야 한다. **실제: alive에 두 alias, 행 1개, 최초 B 1개, A 없음, 두 필드와 명령 모두 실제 이름.**
3. 같은 환경에서 다시 실행해도 중복이나 terminated 판정이 없어야 한다. **실제: running 1개, 추가 A/B 없음.**
4. `HERDR_SESSION_NAME`을 제거한 다음 실행하고, 이어서 존재하지 않는 다른 일회용 이름으로 바꿔 실행해도 YAML의 명시적 서버를 조회해야 한다. **실제: 두 실행 모두 기존 서버 pane을 발견, running 유지, A/B 없음.** 다른 이름의 서버는 자동 시작하지 않도록 설정했다.
5. 임시 기존 행의 두 서버 필드를 default로 설정한 뒤 원래 상속 환경에서 실행하면 기존 행의 처리 방식이 유지되어야 한다. **실제: default 라벨 유지, running, A 없음.**
6. finally에서 탐침 전용 서버와 HOME을 정리하고 잔존 프로세스가 없는지 검사했다. **실제: 정리 및 잔존 검사 통과.**
## 검증 범위의 한계
전체 프로젝트 스위트는 이 잡에서 실행하지 않았다. 장기 실행 데몬을 시작하지 않았으며 환경 변경은 실제 `--once` 연속 실행과 코드 검토로 검증했다. 실제 herdr 탐침의 에이전트 명령은 fake Claude이므로 LLM 업무 수행 자체를 검증하는 시험은 아니다. 이 한계 내에서 요청된 서버 라벨·중복·후속 스캔·create 인수 회귀에 대한 차단 사유는 없다.
[VERDICT: PASS]
@@ -0,0 +1,92 @@
# Code Review Report (4th Round Re-Review): PR #56 (`fix/issue-50-55`)
- **Job ID**: `05f28276`
- **Reviewer**: `agy` (Session: `herdr:reviewer-agy-01`, Role: `Reviewer`)
- **Date**: 2026-10-12
- **Target Pull Request**: PR #56 (`fix/issue-50-55`, Closes #50, #55)
- **Base Commit**: `efd27def47a4d67684694ccf43a6ebeede01620f` (3rd round review head)
- **Head Commit**: `21f0c12b9e2195cba8e196d6ccc2469173302e8a` (Fix commit)
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1`
- **Verification Environment**: macOS (Darwin 24.3.0, arm64), Python 3.9.6, bash 3.2.57(1)-release, `herdr` 0.9.3
---
## 1. Executive Summary
This 4th-round re-review evaluated commit `21f0c12b9e2195cba8e196d6ccc2469173302e8a`, which addresses the regression discovered on macOS during full-suite testing where [`tests/test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1/tests/test_tier4_e2e.py) (6 concurrent creates into the same folder) failed with exit code 4 (`already running in agent-sessions.yaml`).
### Root Cause
During parallel `create_session.sh` invocations, `create_session.sh` runs `reconcile.sh --once` under ambient `HERDR_SESSION_NAME=<slug>`.
1. In `reconcile.sh`, `unique_servers` always includes `'default'`.
2. The scan for `'default'` executes `herdr ls` without `-L`.
3. The `.mam/shim/herdr` wrapper inspects `HERDR_SESSION_NAME`; because it is set to `<slug>`, `_real_herdr` delegates to `"$HERDR_BIN" --session "$session" "$@"`.
4. Thus, the pane list returned by the `'default'` scan actually reflects the panes on server `<slug>`.
5. Previously, `reconcile.sh` drift B labeled these newly discovered panes with `herdr_server: default`.
6. When concurrent `create_session.sh` processes attempted to adopt the auto-registered pane via `_adopted_own`, they required `(s.get('herdr_server') or ...) == server_name` (where `server_name` was `<slug>`). Because the monitor row recorded `'default'`, the comparison failed and `create_session.sh` aborted with exit code 4.
### The Fix
In [`multi-agent-mux-monitor/scripts/reconcile.sh:659-661`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh#L659-L661):
```python
_ambient = os.environ.get('HERDR_SESSION_NAME', '')
if srv == 'default' and _ambient and _ambient != 'default':
srv = _ambient
server_opt = f"-L {srv} " if srv != 'default' else ""
```
If the sweep is querying `'default'` but `HERDR_SESSION_NAME` is set to an explicit server in the environment, `reconcile.sh` recognizes that the shim returned panes from that server, and records `herdr_session: _ambient`, `herdr_server: _ambient`, and `-L _ambient` in the entry.
Additionally, [`tests/data/skill_code_blocks.json`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1/tests/data/skill_code_blocks.json) was updated to line 550 for `SystemExit(4)` in `create_session.sh`.
**Verdict: PASS**.
---
## 2. Detailed Technical Verification
### 2.1 Correctness of the Ambient Server Attribution
- In `.agents/skills/lib.sh:271-280`:
```bash
_real_herdr() {
local session="${HERDR_SESSION_NAME:-}"
if [ "$session" = "default" ]; then session=""; fi
if [ -n "$session" ]; then
if [ "${1:-}" = "--session" ] || [ "${1:-}" = "-s" ]; then "$HERDR_BIN" "$@"; else "$HERDR_BIN" --session "$session" "$@"; fi
else
"$HERDR_BIN" "$@"
fi
}
```
Calls to `herdr` without `-L` or `--session` are answered by `herdr --session "$session"`. Thus, attributing panes found in a `'default'` scan to `HERDR_SESSION_NAME` is strictly faithful to the actual server queried by `_real_herdr`.
### 2.2 Side-Effect Analysis
1. **Drift A (Termination Detection)**:
- When a row is registered with `herdr_server: <slug>`, subsequent sweeps populate `unique_servers` from `d.get('herdr_sessions')`, adding `<slug>`.
- `reconcile.sh` explicitly scans `-L <slug>` and places `(name, '<slug>')` into `alive_set`.
- In drift A, `(s.get('name'), s.get('herdr_server'))` matches `alive_set`. The row is **not** falsely terminated.
2. **Daemon Mode (No Ambient Server)**:
- In persistent monitor daemon runs (`reconcile.sh --subscribe`), `HERDR_SESSION_NAME` is empty or `'default'`.
- `_ambient and _ambient != 'default'` evaluates to `False`, preserving exact default behavior.
3. **Duplicate Registration Prevention**:
- In drift B, [`yaml_session_names.add(name)`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh#L715) is updated immediately when a pane is registered.
- If the same pane is enumerated in both the `'default'` scan and an explicit `-L <slug>` scan within the same sweep, line 611 skips the duplicate (`if (name, srv) in yaml_session_names or any(_sanitize(y) == name for y in yaml_session_names): continue`). No duplicate entries are created.
### 2.3 Verification of Fixed Tests
1. **`test_e2e_scenario4_parallel_flock_locking`**:
- Executed [`tests/test_tier4_e2e.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261012T013821Z-656dfe/1/tests/test_tier4_e2e.py): all 5 scenarios passed in 364.51s, with scenario 4 completing in 42.28s without exit code 4.
2. **`tests/test_issue50_pane_lock.py`**:
- Executed full test suite: 11 / 11 passed in 53.20s.
3. **`tests/test_issue42_skill_blocks.py`**:
- Executed: 37 passed, 1 skipped. Line 550 in `tests/data/skill_code_blocks.json` matches `create_session.sh` exactly.
---
## 3. Test Execution Summary
| Test Target | Result | Duration |
|---|---|---|
| `tests/test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking` | **1 Passed** | 42.28s |
| `tests/test_tier4_e2e.py` (Full Suite, 5 Scenarios) | **5 Passed** | 364.51s |
| `tests/test_issue50_pane_lock.py` (Full Suite) | **11 Passed** | 53.20s |
| `tests/test_issue42_skill_blocks.py` | **37 Passed, 1 Skipped** | 0.17s |
---
[VERDICT: PASS]
@@ -0,0 +1,36 @@
# Review 51507ec8 — the monitor labels a row with the server that answered
Head `21f0c12b9e2195cba8e196d6ccc2469173302e8a` (tree `c2357ac01824d099fcbb7e9f7433b6b05f5e86a6`) is the one commit `21f0c12 fix(issue-50): the monitor labels a row with the server that answered` on base `efd27def47a4d67684694ccf43a6ebeede01620f`. Two files, +9/−1. `git diff --check` is clean. Snapshot porcelain empty. Control-plane main is still `42e51735ba285930fc5b8ee70ba343277e9b1adb`, porcelain empty.
Drift B writes the row after `pane_meta` and the cwd check. When the scan key is `default` and `HERDR_SESSION_NAME` is set to something else, that name replaces `srv` before `server_opt` and the row are built (`reconcile.sh:664-666`). `herdr_session`, `herdr_server`, and the start, attach, and kill commands all use that name (`:681-687`). The scan (`:505-509`), `pane_meta` (`:540-544`), and drift A (`:594-595`) still use the scan key.
## Why the label matches the server that answered
`unique_servers` always contains `default` and, when the variable is set, `HERDR_SESSION_NAME` (`reconcile.sh:494-496`). A `default` scan runs `herdr ls` with no `-L`. The shim's `ls` reads `HERDR_SESSION_NAME` (`lib.sh:1256-1259`) and calls `_real_herdr agent list` (`:1274`). `_real_herdr` prepends `--session` whenever that value is non-empty (`:270-283`; `default` is treated as empty at `:272-274`). A create exports `HERDR_SESSION_NAME` (`create_session.sh:373`) and its end-of-run `reconcile.sh --once` is a child of that process (`:748`), so the inherited value is the create's server.
On this Mac, herdr 0.9.3, throwaway home `/tmp/m515h2`, session `t515b`: the server socket appeared under that home. `herdr --session t515b agent list` exited 0. `herdr agent list` with no `--session` exited 1 with `server_not_running` for `/tmp/m515h2/.config/herdr/herdr.sock`. The generated shim's `ls`, with `HERDR_SESSION_NAME=t515b`, no `-L`, and `MAM_NO_AUTOSTART=1`, exited 0. The same `ls` with the variable unset exited 1 and printed `no server running (herdr session default)`. `workspace create` on that server returned a pane; `agent list` stayed an empty agent list, so the shim's `ls` printed no name. The exit codes are the routing check. No socket named `t515b` appeared under the user home.
Create adopts a monitor row when `herdr_server or herdr_session or 'default'` equals its own `HERDR_SESSION_NAME` (`create_session.sh:522`, `:540-543`). After this label they are the same string, the epoch check is unchanged, and the row is replaced. A row that fails that test still raises `SystemExit(4)` at `create_session.sh:550`. `tests/data/skill_code_blocks.json` now points exit code 4 at that line.
## Side effects
A throwaway workspace under `/tmp/m515ws` ran this snapshot's `reconcile.sh --once`. The shim was the one `lib.sh` writes, and the binary it called was a stand-in that returns one pane. `HERDR_SESSION_NAME=t515probe`.
- Both scans called `agent list --session t515probe`. `default` sorts first, so the first call is the `default` scan. The result listed the pane under both scan keys, `probe-creator-claude|default` and `probe-creator-claude|t515probe`. The yaml received one row, `status: running`, `herdr_server` and `herdr_session` `t515probe`, note `auto-registered by monitor`, kill command `herdr -L t515probe kill-session -t probe-creator-claude`. `pane_meta` ran once. The second sighting is skipped because the name is added to `yaml_session_names` after the append (`reconcile.sh:715`) and the next entry hits the name check (`:610-611`).
- A second `--once` with `HERDR_SESSION_NAME` unset scanned the real default (the stand-in was asked for `agent list` with no `--session`) and the row's server (`--session t515probe`). The live set was `probe-creator-claude|t515probe`. The row stayed `running` on `t515probe`. Drift A did not fire. Drift A compares `(name, herdr_session or herdr_server or 'default')` with the scan keys (`:594-595`), and the daemon's server set includes each row's `herdr_session` (`:499-502`).
- With no `HERDR_SESSION_NAME`, a new pane visible only on the real default scan was stored as `herdr_server: default` and the kill command has no `-L`. The new `if` does not run.
- A row already stored as `default` stayed `running`, stayed `default`, and kept its note. The new `if` is on the new-entry path, past the name skip.
A create's own `--once` still puts `default` in the scan set, and that scan is still answered from the inherited `HERDR_SESSION_NAME`. A row whose server is the real default is compared with that redirected scan for that one pass. The commit leaves the scan and drift A as they were. The row this commit writes is looked up on the following daemon pass by the server name stored on it.
## What was run
On this Mac, with the pr-56-fix venv (Python 3.9.6), `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp` under `/tmp/m515bt-s4`, `/tmp/m515bt-lock`, and `/tmp/m515bt-skill`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `WORKSPACE_ROOT`, `CLAUDE_PROJECT_DIR`, `HOME_DIR`, and `CODEX_HOME` were unset.
- `tests/test_tier4_e2e.py::test_e2e_scenario4_parallel_flock_locking`: 1 passed in 43.01s. Six parallel creates in one sandbox all exited 0, and the yaml held those six names. The mock herdr drops `-L` and `--session`, so this is the regression the full suite hit at `a9ffeda` (rc 4, "already running"). The shim routing above is the separate check that a no-`-L` call reaches `HERDR_SESSION_NAME`.
- `tests/test_issue50_pane_lock.py`: 11 passed in 54.30s. That includes the adoption of a monitor row of this server and the refusal of another server's row.
- `tests/test_issue42_skill_blocks.py::test_exit_table_agrees_with_script`: 3 passed in 0.05s. The passthrough for exit code 4 resolves to `create_session.sh:550`, and that line raises `SystemExit(4)`.
User `~/.config/herdr/config.toml` mtime stayed `1784456394`. The herdr `sessions` listing hash stayed `0e4d261c4faae218c1e6ec7f2914c0c766df456e0c5e460cb28e2dcb20385177`. The control-plane loop marker mtime stayed `1791769101`. The throwaway server was stopped and its socket removed.
[VERDICT: PASS]