release(v4.9.1): tests no longer reach a real herdr server - archive the review reports and bump the lockstep version
Review loop (--review-only, --all-reviewer): round 1 (@325b056) 4/4 PASS,
round 2 (@db7f4ca, HERDR_SESSION) 4/4 PASS. Full suite at 325b056 on
macOS / Python 3.9 / bash 3.2 / herdr 0.9.3 from an agent pane:
1845 passed / 0 failed / 56 skipped (18 test_deploy_freshness skips: no
nats-docker submodule in the fresh worktree); the panes of the real
servers (default, multi-agent-mux) and ~/.claude/projects were unchanged.
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
db7f4cab9c
commit
50c8da8269
32 files changed
+607
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T001819Z-131fa6",
|
||||
"base_sha": "8b0b4a07dc76a4915895bdde6170dc8eed9b5f74",
|
||||
"head_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"tree_sha": "7905896f005159d596e3872d0ee2c3c12061f09a",
|
||||
"review_commit": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "63919c5d",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T00:57:46Z"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# 테스트의 실제 herdr 서버 누출 수정 리뷰 (job 63919c5d)
|
||||
|
||||
- 리뷰어: planner-reviewer-claude-01 (Reviewer)
|
||||
- 리뷰 identity: loop-20261013T001819Z-131fa6 / base 8b0b4a07 .. head 325b0560 (커밋 2개: 525df2b 수정, 325b056 s11 기준 이동 / 4 파일) / tree 7905896f
|
||||
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1` — HEAD·tree 가 identity 와 일치, `git status --porcelain` 0 줄(시작·종료 시), `.mam` 디렉터리도 생기지 않음. 본 체크아웃 HEAD 8b0b4a0(main) 변동 없음.
|
||||
- 실행 안전: 내 pane 은 `HERDR_SOCKET_PATH`·`HERDR_PANE_ID`·`HERDR_TAB_ID`·`HERDR_WORKSPACE_ID`·`HERDR_SESSION_NAME`·`HERDR_SESSION`·`HERDR_CONFIG_PATH`·`HERDR_BIN_PATH`·`HERDR_ENV` 를 물려받고 있어, 모든 테스트·탐침은 `env -i`(PATH/HOME/LANG/TMPDIR 만)로 돌려 브리프의 `-u` 목록을 포함한 전부를 비웠다. basetemp 는 세션 scratchpad 아래(경로에 "shim" 없음). 실제 바이너리를 부른 것은 일회용 HOME 에서의 `status server`(읽기 전용, 서버 미기동)뿐이다. Python 은 본 체크아웃 `.venv`(3.9.6, paho 2.1.0). `.mam.env` 는 읽지 않았다.
|
||||
|
||||
## 1. 원인 판정이 맞는가
|
||||
|
||||
**원인 1 (조상 `*-shim` 이 mock 을 가림).** base 의 `_is_shim_path` 는 `*-shim/*` 를 `"/$1/"` 전체에 대해 매칭해 조상 디렉터리까지 걸렀고(`lib.sh` base), 생성 shim 의 `_resolve_real_herdr` 도 `[[ "$dir" != *"-shim"* ]]` 로 PATH 항목 문자열 어디든 걸렀다. basetemp `pytest-pr56-shim` 아래 mock `bin/` 은 둘 다에서 건너뛰어졌고 다음 후보인 `/opt/homebrew/bin/herdr` 가 "real" 이 된다. 수정 전 lib.sh 로 되돌린 scratch 복사본에서 L1 이 정확히 그 상태로 실패했다: `_resolve_real_herdr_path` 가 `…/pytest-x-shim/case0/bin/herdr` 대신 다음 PATH 항목의 디코이 `…/decoy/bin/herdr` 를 골랐다(`assert '/…/decoy/bin/herdr' == '/…/case0/bin/herdr'`). 판정 맞음.
|
||||
|
||||
**원인 2 (pane 의 `HERDR_SOCKET_PATH`).** 실제 herdr 0.9.3 바이너리를 일회용 HOME 에서 `status server` 로 읽기만 해 확인했다:
|
||||
|
||||
| 환경 | 바이너리가 가리킨 소켓 |
|
||||
|---|---|
|
||||
| `HERDR_SOCKET_PATH=/nonexistent/s.sock` + `--session t-zz` | `/nonexistent/s.sock` |
|
||||
| `HERDR_SOCKET_PATH=/nonexistent/s.sock` + `HERDR_SESSION=t-zz` | `/nonexistent/s.sock` |
|
||||
| `HERDR_SESSION=t-zz` 만 | `$HOME/.config/herdr/sessions/t-zz/herdr.sock` (가짜 HOME 아래) |
|
||||
| `HERDR_SOCKET_PATH` 만 | `/nonexistent/s.sock` |
|
||||
|
||||
즉 `HERDR_SOCKET_PATH` 는 **`--session` 보다도** 우선한다. shim 이 항상 `--session <샌드박스 서버>` 를 붙여도 이 변수가 남아 있으면 실제 서버로 간다 — 원인 1 로 실제 바이너리가 선택되는 순간 샌드박스 HOME 은 아무 보호가 되지 못했다는 오케스트레이터의 실측과 일치한다. 두 원인은 독립적이고 둘 다 막아야 한다(1 만 고치면 pane 변수 누출 시 다른 경로의 실제 호출이 여전히 실제 서버로, 2 만 고치면 숨은 mock 대신 실제 바이너리가 가짜 HOME 에 서버를 띄움). 판정 맞음.
|
||||
|
||||
## 2. 수정 내용 검토
|
||||
|
||||
- `lib.sh:113-124 _is_shim_path`: 첫 `case` 에서 `*-shim/*` 만 빠지고 `.mam/shim`·`$_HERDR_SHIM_DIR_PATTERN`(`/multi-agent-herdr-shim/`)·`$_HERDR_SKILLS_BIN_PATTERN`(`/.agents/skills/.bin`) 은 그대로. `*-shim` 은 `"${1%/}"` 즉 인자 자신의 끝에만 적용. `lib.sh:130-136 _resolve_real_herdr_path` 는 PATH 항목(`_is_shim_path "$dir"`)과 후보 파일의 **canonical dirname** 둘 다 검사한다 — L1 의 mock 은 `tmp/bin/herdr` 로의 symlink 라 canonical dirname 은 `tmp/bin` 이고, 선택 결과는 PATH 항목 쪽 경로(`$dir/herdr`)다. 기존 래퍼 디렉터리 제외 여부: `.mam/shim` → 첫 case(부분 문자열, 조상 포함) 유지; `multi-agent-herdr-shim` → 패턴 상수 유지(`"/…/multi-agent-herdr-shim/…/"`); `.agents/skills/.bin` → 패턴 유지(이 저장소에는 그 디렉터리가 없다); 테스트의 `herdr-shim`(L1b)·`py-shim`·`ps-shim`(`test_issue17_item4_decisions.py:208`, `test_issue21_acceptance.py:283`, `test_issue30_stall_watch_stop.py:95`) 은 디렉터리 자신의 이름이 `*-shim` 이라 계속 제외.
|
||||
- 생성 shim(`lib.sh:173-178`): `[[ "${dir%/}" != *-shim ]]`. `.mam/shim` 부분 문자열 검사는 그대로.
|
||||
- `tests/conftest.py:293-298 mam_sandbox`: `HERDR_SOCKET_PATH`·`HERDR_PANE_ID`·`HERDR_TAB_ID` 제거. 기존의 `HERDR_SESSION_NAME`·`HERDR_SERVER_NAME`·`HERDR_WORKSPACE_ID`·`HERDR_CONFIG_PATH`·`XDG_CONFIG_HOME` 제거와 합쳐 pane 이 내보내는 라우팅 변수 중 실제 서버에 닿을 수 있는 것은 모두 빠진다(아래 비차단 메모의 `HERDR_SESSION` 참조).
|
||||
- bash 3.2: `${1%/}`·`${dir%/}`·`case` 뿐, `bash -n` 통과. Python 3.9 컴파일 통과.
|
||||
- s11 기준 커밋 525df2b: 생성 shim heredoc 이 바뀌었으므로 기준을 그 커밋으로 옮긴 것은 #45 때와 같은 방식이고, 검증 경로에서 s11 PASSED. 525df2b 는 이 브랜치의 첫 커밋으로 검증 경로에 존재한다.
|
||||
|
||||
## 3. 충분한가 — 다른 경로로 실제 서버에 닿는 테스트
|
||||
- `mock_herdr`(`conftest.py:310`)·`mock_agents`(`:1412`) 는 `mam_sandbox` 에 의존하므로 mock 을 PATH 에 두는 테스트는 모두 샌드박스 환경(라우팅 변수 제거, HOME 교체)이다.
|
||||
- `mam_sandbox` 없이 herdr 를 흉내 내는 파일은 `test_issue14_delivery_ack.py`·`test_issue18_liveness_contract.py` 둘(bash 함수 `_sks_herdr` 대체, HOME=tmp). 이 둘이 herdr **바이너리**를 한 번이라도 부르는지 보려고 호출을 기록하는 디코이 `herdr` 를 PATH 맨 앞에 두고 두 파일을 돌렸다: **43 passed, 디코이 호출 0 건**(로그 파일이 생기지 않음). 즉 이 둘은 라우팅 변수가 남아도 바이너리를 실행하지 않는다. (이 실행의 `1 error` 는 세션 teardown 의 #55 agent-home 가드가 `~/.gemini/antigravity-cli/conversations/f9bf3d55-….db-shm/.db-wal` 을 잡은 것으로, 브리프가 예고한 다른 워크스페이스의 실행 중 agy 대화 WAL/SHM 이다. 테스트 산출물이 아니므로 판정에 쓰지 않고 보고만 한다.)
|
||||
- `test_issue6_fail_closed.py`·`test_issue6_turn_handshake.py` 는 `mock_herdr` 를 쓰므로 샌드박스다.
|
||||
- 실제 바이너리가 아는 라우팅 변수는 `HERDR_SOCKET_PATH`(최우선)와 `HERDR_SESSION`(HOME 상대 경로)뿐임을 위 표로 확인했다. `HERDR_SESSION_NAME` 은 바이너리가 모르고 shim 만 `--session` 으로 바꾼다(어제 리뷰에서 확인). 따라서 `HERDR_SOCKET_PATH` 제거 + 가짜 HOME 이면 shim 경로든 직접 호출이든 실제 서버에는 닿지 않는다.
|
||||
|
||||
## 4. 테스트 결과
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| head: `test_herdr_real_server_leak.py` + `test_issue23_24_startup_path.py` + `test_herdr_shim_contract.py` + `test_issue17_workspace_root_isolation.py` + `test_issue9_server_env.py` + `test_issue45_herdr_config.py` | **130 passed, 34 skipped**(skip 은 모두 `needs tomllib (Python 3.11+)` 류의 알려진 3.9 skip). L1·L1b·L2·s11 은 모두 PASSED 로 실행됨 |
|
||||
| 수정 전 lib.sh(8b0b4a0) + 새 테스트 | **L1 실패**(디코이 선택), **L1b 통과**(자기 이름 `*-shim` 은 전후 모두 제외) |
|
||||
| 수정 전 conftest(8b0b4a0) + 새 테스트 | **L2 실패**: `['HERDR_PANE_ID', 'HERDR_SOCKET_PATH', 'HERDR_TAB_ID']` 가 남음 |
|
||||
| 디코이 sweep(`test_issue14`, `test_issue18`) | 43 passed, 디코이 호출 0 |
|
||||
|
||||
수정 전 재현에는 실제 herdr 가 실행될 수 있는 구성을 쓰지 않았다(테스트 설계대로 디코이가 PATH 에서 실제 바이너리보다 앞서고, `env -i` 로 소켓 변수도 없음).
|
||||
|
||||
## 5. 공유 제어 평면에 주는 영향
|
||||
- 생성 shim 은 lib.sh 를 source 하는 모든 실행에서 `<워크스페이스>/.mam/shim/herdr` 로 다시 쓰이므로, 머지 뒤 실제 제어 평면의 shim 도 두 줄이 바뀐다. 바뀌는 것은 "조상 이름에 `-shim` 이 든 PATH 항목을 더 이상 건너뛰지 않음" 뿐이고, `.mam/shim` 과 자기 이름이 `*-shim` 인 디렉터리는 계속 건너뛴다. 내 pane 의 PATH 에서 `-shim` 을 포함하는 항목은 `~/.cmuxterm/cmux-cli-shims/<uuid>` 하나인데(`herdr` 없음, `amp claude codex hermes pi` 만) 전후 어느 규칙에도 해당하지 않아 영향 없다. 본 체크아웃의 `.mam/shim/herdr` 는 이 리뷰 중 건드리지 않았다(아직 base 내용).
|
||||
- 비차단 메모 1: README(`README.md:102`, `README.ko.md:84`)가 설명하는 레거시 레이아웃 `/tmp/multi-agent-herdr-shim/<server>/herdr` 는 디렉터리 자신의 이름이 서버명이라, 생성 shim 의 새 규칙으로는 제외되지 않는다(lib.sh 의 `_is_shim_path` 는 패턴 상수로 계속 제외). 현재 코드는 그 레이아웃을 만들지 않고 이 호스트에도 없으므로 동작 영향은 없지만, 생성 shim 에도 `*"/multi-agent-herdr-shim/"*` 를 두거나 README 를 현재 레이아웃(`.mam/shim`)으로 고치면 깔끔하다.
|
||||
- 비차단 메모 2: `mam_sandbox` 는 `HERDR_SESSION`(바이너리가 아는 변수, 내 pane 도 내보냄)을 지우지 않는다. 가짜 HOME 아래 경로로 풀리므로 실제 서버 누출은 아니지만, `lib.sh:1425 _herdr_caller_server` 와 `pane_lock.caller_server` 가 이 값을 "호출자 서버" 로 읽어 pane 안/밖에서 테스트 분기가 달라질 수 있다. 결정성을 위해 함께 지우는 것을 권한다(이 PR 의 범위 밖).
|
||||
|
||||
## 6. 잔류물
|
||||
- scratch 복사본·basetemp·디코이·로그는 모두 세션 scratchpad 아래. 일회용 HOME `/tmp/j639-h` 제거(서버를 띄운 적 없음). 실제 `.mam`·`.mam.env`·모니터·실제 서버(multi-agent-mux, default)·`~/.config/herdr`·에이전트 홈은 건드리지 않았다.
|
||||
|
||||
## 결론
|
||||
두 원인 판정은 코드와 실측(특히 `HERDR_SOCKET_PATH` 가 `--session` 보다 우선)으로 확인되고, 수정은 각각을 막으며 회귀 테스트는 수정 전 코드에서 정확히 그 이유로 실패한다. 기존 래퍼 디렉터리 제외는 유지되고, mock 없이 돌아가는 두 테스트 파일은 herdr 바이너리를 아예 부르지 않는다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T010208Z-f90dd4",
|
||||
"base_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"head_sha": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"tree_sha": "f32cabd3a6c8d775c9223509967f1a810f0cf827",
|
||||
"review_commit": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "834fa032",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T01:21:50Z"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# 테스트 실제 herdr 서버 누수 수정 2차 리뷰 — 커밋 db7f4ca (job 834fa032)
|
||||
|
||||
- 리뷰어: planner-reviewer-claude-01 (Reviewer)
|
||||
- 리뷰 identity: loop-20261013T010208Z-f90dd4 / base 325b0560 .. head db7f4cab (커밋 1개, 2 파일) / tree f32cabd3
|
||||
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1` — HEAD·tree 일치, `git status --porcelain` 0 줄(시작·종료), `.mam` 생성 없음. 본 체크아웃 HEAD 8b0b4a0(main) 변동 없음.
|
||||
- 실행 안전: 1차와 같이 모든 테스트·탐침은 `env -i`(PATH/HOME/LANG/TMPDIR 만, 필요한 변수는 명시 추가)로 돌려 브리프의 `-u` 목록 전부가 비워진다. basetemp 는 scratchpad 아래(이름에 "shim" 없음). 실제 바이너리는 일회용 HOME 에서 `status server` 읽기만(서버 미기동). Python 은 본 체크아웃 `.venv`(3.9.6). 전체 스위트는 돌리지 않았다. 범위는 이 커밋만.
|
||||
|
||||
## 1. 변경
|
||||
- `tests/conftest.py:299`: `mam_sandbox` 가 `HERDR_SESSION` 도 지운다(주석 `:296-297`). 현재 제거 목록: `HERDR_CONFIG_PATH`, `HERDR_PANE_ID`, `HERDR_SERVER_NAME`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `HERDR_SOCKET_PATH`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `XDG_CONFIG_HOME`.
|
||||
- `tests/test_herdr_real_server_leak.py:76`: L2 의 `PANE_VARS` 에 `HERDR_SESSION` 추가. 수정 전 conftest(325b056)에 새 테스트를 얹은 scratch 복사본에서 L2 는 `AssertionError: ['HERDR_SESSION']` 로 실패 → 테스트가 이 변경을 정확히 겨눈다.
|
||||
- Python 3.9 컴파일 통과. 제품 코드 변경 없음.
|
||||
|
||||
## 2. HERDR_SESSION 을 지워서 깨지는 테스트가 있는가 (검토 관점 1)
|
||||
- 소비자: `lib.sh:1425 _herdr_caller_server`, `lib_py/pane_lock.py:49 caller_server`, `lib_py/loop/watcher.py:64` — 셋 다 "있으면 호출자 서버, 없으면 `HERDR_SOCKET_PATH` 모양" 이고 `HERDR_SOCKET_PATH` 는 이미 지워지므로 샌드박스에서는 일관되게 "pane 밖" 으로 판정된다.
|
||||
- 테스트 쪽 bare `HERDR_SESSION` 참조는 전부 테스트가 **스스로** 넣는 값이다(픽스처 뒤라 영향 없음): `test_issue23_24_startup_path.py:264,369,395`(S1g·watcher 케이스는 `env={...}` 로 명시), `test_issue34_job_tree.py:580`, `test_issue30_stall_watch.py:486`, `test_issue45_restore.py:42`(`SCOPE_ENV` 로 이미 `HERDR_SESSION`·`HERDR_ENV` 까지 자체 제거). 반대로 "`HERDR_SESSION` 이 없어야" 하는 테스트(`test_s2b…:188`, S2 계열 "pane 밖 호출자") 는 이제 pane 안에서 돌려도 전제가 보장된다 — 이것이 이 변경의 목적이다.
|
||||
- 실측: 같은 6 파일(`test_herdr_real_server_leak`, `test_issue50_pane_lock`, `test_issue23_24_startup_path`, `test_herdr_shim_contract`, `test_issue45_herdr_config`, `test_issue9_server_env`)을 (a) 깨끗한 환경, (b) 바깥 환경에 `HERDR_SESSION=pane-real-srv` 를 넣은 환경에서 각각 돌렸다: **둘 다 139 passed, 34 skipped** (skip 은 모두 Python 3.9 의 tomllib skip). pane 안/밖 결과가 같다.
|
||||
|
||||
## 3. 다른 라우팅 변수가 남았는가 (검토 관점 2)
|
||||
- 내 pane 이 내보내는 `HERDR_*` 는 `HERDR_BIN_PATH`, `HERDR_CONFIG_PATH`, `HERDR_ENV`, `HERDR_PANE_ID`, `HERDR_SESSION`, `HERDR_SESSION_NAME`, `HERDR_SOCKET_PATH`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID` 아홉 개. 샌드박스가 지우지 않는 것은 `HERDR_BIN_PATH`·`HERDR_ENV` 둘뿐이다.
|
||||
- 실제 바이너리(0.9.3) 에 각각을 단독으로 주고 `status server` 를 읽었다: `HERDR_ENV`, `HERDR_BIN_PATH`, `HERDR_CONFIG_PATH`, `HERDR_WORKSPACE_ID`, `HERDR_PANE_ID` 모두 소켓은 `$HOME/.config/herdr/herdr.sock`(가짜 HOME) — 서버 선택에 영향 없음. 1차에서 확인한 대로 소켓을 바꾸는 것은 `HERDR_SOCKET_PATH`(최우선)와 `HERDR_SESSION`(HOME 상대) 둘뿐이며 이제 둘 다 제거된다.
|
||||
- `.agents`·`tests` 에서 `HERDR_BIN_PATH`·`HERDR_ENV` 를 읽는 코드는 없다(`test_issue45_restore.py:42` 가 자체 정리 목록에 넣어 둔 것뿐). 남은 라우팅 변수 없음.
|
||||
|
||||
## 4. 잔류물
|
||||
- scratch 복사본·basetemp·로그는 scratchpad 아래. 일회용 HOME `/tmp/j834-h` 제거(서버 미기동). 실제 `.mam`·`.mam.env`·모니터·실제 서버·`~/.config/herdr`·에이전트 홈은 건드리지 않았다.
|
||||
|
||||
## 결론
|
||||
`HERDR_SESSION` 제거는 호출자 서버 판정을 pane 안/밖에서 동일하게 만들고, 그 값을 쓰는 테스트는 모두 자체 설정이라 깨지지 않는다(두 환경에서 동일하게 139 passed). 실제 바이너리의 서버 선택에 관여하는 변수는 이제 모두 샌드박스에서 제거된다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T010208Z-f90dd4",
|
||||
"base_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"head_sha": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"tree_sha": "f32cabd3a6c8d775c9223509967f1a810f0cf827",
|
||||
"review_commit": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"reviewer": "planner-reviewer-codex-01",
|
||||
"job_id": "b1696427",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T01:21:50Z"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# Job b1696427 — HERDR_SESSION isolation review
|
||||
|
||||
판정: PASS. 지정된 두 파일 변경에서 차단할 결함을 발견하지 않았다. 관련 테스트 **84건 모두 통과**했고, 별도 base/head 탐침에서 상속 세션 제거와 fixture 이후의 명시적 설정 보존을 확인했다.
|
||||
|
||||
## 검증 identity
|
||||
|
||||
- loop: `loop-20261013T010208Z-f90dd4`
|
||||
- base: `325b056057fcdd717d2c81017fc5ef57183cb4d0`
|
||||
- head: `db7f4cab9c60ff5db009cf3fc625e9a86a465ef6`
|
||||
- tree: `f32cabd3a6c8d775c9223509967f1a810f0cf827`
|
||||
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1`
|
||||
- Python: `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python`
|
||||
|
||||
HEAD/tree가 지정 identity와 일치하고 `git status --short`는 비어 있었다. `git diff --check 325b056`도 통과했다. 이 커밋만 검토했으며 운영 코드·생성 shim 변경은 없다. 소스 수정, creator 작업 트리 실행, 커밋·push는 하지 않았다.
|
||||
|
||||
## 코드 검토
|
||||
|
||||
1. `tests/conftest.py:298–301`에서 기존 socket/pane/tab 제거 사이에 `monkeypatch.delenv("HERDR_SESSION", raising=False)`를 추가한 것은 적절하다. 값이 없는 실행도 허용하고, pytest monkeypatch의 기존 복원 수명 주기를 따른다. `.agents/skills/lib.sh:1425–1427`, `lib_py/pane_lock.py:49–50`, `lib_py/loop/watcher.py:64–66`은 이 값을 우선해서 caller server로 읽으므로 sandbox 시작 전에 상속값을 제거해야 pane 안/밖의 기본 분기가 같아진다.
|
||||
2. `tests/test_herdr_real_server_leak.py:73–88`은 새 변수를 가짜 pane 환경에 넣은 뒤 실제 `mam_sandbox` 이후 남지 않는지 확인한다. 외부 실행 환경을 미리 scrub했더라도 시험이 스스로 값을 주입하므로 추가된 제거 동작을 검증한다.
|
||||
3. 테스트가 의도적으로 지정하는 값은 보존된다. `test_issue23_24_startup_path.py:31–42`의 helper는 fixture 이후 subprocess 환경을 구성하고 명시적 `env`를 마지막에 적용한다. `:261–266`의 HERDR_SESSION 우선순위 시험을 포함한 모듈이 통과했다. `test_issue50_pane_lock.py:63–80`도 명시적인 환경 mapping으로 caller/lock 식별을 검사한다. 추가 탐침에서도 fixture 이후 설정한 값이 두 helper에 그대로 전달됐다.
|
||||
4. 확인한 라우팅·범위 변수 `HERDR_SESSION`, `HERDR_SESSION_NAME`, `HERDR_SERVER_NAME`, `HERDR_WORKSPACE_ID`, `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`와 설정 경로 `HERDR_CONFIG_PATH`, `XDG_CONFIG_HOME`은 모두 `conftest.py:290–306`에서 제거된다. 실제 fixture setup에 이 9개를 주입한 독립 탐침에서도 head에는 하나도 남지 않았다. 검사한 repository의 caller/scope/config 경로에서 추가 누락 라우팅 변수는 찾지 못했다. 이는 알려진 경로의 검증이며 임의의 미래 herdr 환경변수까지 일괄 제거한다는 의미는 아니다.
|
||||
|
||||
`mam_sandbox`를 사용하지 않는 테스트의 환경 계약은 이 두 파일 변경으로 바뀌지 않는다. 예를 들어 실제 서버용 `test_issue50_dispatch_herdr.py`는 모든 HERDR_*를 별도로 제거하고, `test_issue45_restore.py`의 Rig도 HERDR_SESSION을 포함한 scope 변수를 자체 제거한다. 이 실제 서버 모듈은 이번 리뷰에서 실행하지 않았다.
|
||||
|
||||
## 실행 검증
|
||||
|
||||
안전 launcher `/tmp/b1696427-review/run.py`를 사용해 아래 관련 모듈만 실행했다.
|
||||
|
||||
```text
|
||||
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE <Python> /tmp/b1696427-review/run.py tests/test_herdr_real_server_leak.py tests/test_issue50_pane_lock.py tests/test_issue23_24_startup_path.py
|
||||
```
|
||||
|
||||
- **84 passed in 195.48s**, exit 0. skip/failure 없음.
|
||||
- 로그: `/tmp/b1696427-review/tests.log`
|
||||
- basetemp: `/tmp/b1696427-mxmhd4h3/bt` — 검증 트리 밖이며 이름에 shim 없음.
|
||||
- `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 일회용 HOME, 지정 AGENT_PYTHON_BIN 사용.
|
||||
- launcher는 상속 HERDR_*/MAM_*/MQTT_*/SKS_* 및 제어·에이전트 경로 환경을 추가로 제거했다. 실제 설치 herdr에 도달하기 전에 기록 후 exit 97로 중단하는 fallback 디코이를 PATH에 두었다. **fallback 호출 기록 없음.** 테스트별 mock과 suite의 로컬 broker를 사용했다.
|
||||
- 전체 스위트, 실제 herdr, 운영 모니터·서버는 실행하지 않았다. `.mam.env` export/출력이나 실제 사용자 에이전트 홈 변경도 하지 않았다.
|
||||
|
||||
## 독립 base/head 탐침
|
||||
|
||||
산출물: `/tmp/b1696427-review/probe.py`, `/tmp/b1696427-review/probe.log`.
|
||||
|
||||
재현은 위와 같은 `env -u ...` 및 `PYTHONDONTWRITEBYTECODE=1`로 지정 Python에서 probe.py를 실행한다. 검증 트리의 git base와 head에서 `mam_sandbox` 함수 AST를 추출해 pytest decorator만 제거하고, 실제 함수 본문을 별도 `/tmp` 디렉터리에 실행했다. `pytest.MonkeyPatch`를 사용하고, 이 setup에서 시작하는 데몬이 없으므로 등록되는 reaper finalizer는 실행하지 않았다. 서버를 실행하는 코드는 호출하지 않았다.
|
||||
|
||||
절차와 결과:
|
||||
|
||||
1. fixture 전에 위 9개 변수에 안전한 sentinel 값을 주입한다.
|
||||
2. base fixture 실행 후 남은 변수는 `HERDR_SESSION` 하나이고 값은 `caller-sentinel`이었다. 실제 Python `pane_lock.caller_server`와 검증 트리에서 추출한 셸 `_herdr_caller_server` 모두 `caller-sentinel`을 반환했다.
|
||||
3. head fixture 실행 후 남은 변수는 없었다. 두 helper 모두 빈 값을 반환했다. 기대한 기본 호출자 분기 복원이 확인됐다.
|
||||
4. 각 fixture 실행 뒤 `HERDR_SESSION=chosen-by-test`를 설정하면 두 helper 모두 `chosen-by-test`를 반환했다. 명시적 테스트 설정은 제거되지 않는다.
|
||||
|
||||
탐침 exit 0, 모든 assertion 통과. 임시 root는 `/tmp/b1696427-probe-6o1gbryi`였다. 이전 리뷰의 결과를 현재 커밋 검증으로 대체하지 않고 이번 identity에서 새로 실행했다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T001819Z-131fa6",
|
||||
"base_sha": "8b0b4a07dc76a4915895bdde6170dc8eed9b5f74",
|
||||
"head_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"tree_sha": "7905896f005159d596e3872d0ee2c3c12061f09a",
|
||||
"review_commit": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"reviewer": "planner-reviewer-codex-01",
|
||||
"job_id": "efb52908",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T00:57:46Z"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Job efb52908 — herdr leak fix review
|
||||
|
||||
판정: PASS. 지정된 2개 커밋/4개 파일에서 이번 변경을 차단할 결함은 발견하지 않았다. 관련 테스트 **105 passed, 1 deselected**이고, base/head 디코이 탐침으로 경로 선택 수정과 기존 래퍼 제외 동작을 확인했다. 아래 기존 해석기 차이와 운영상 한계는 이번 변경의 회귀와 구분한다.
|
||||
|
||||
## 검증 identity 및 범위
|
||||
|
||||
- loop: `loop-20261013T001819Z-131fa6`
|
||||
- base: `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`
|
||||
- head: `325b056057fcdd717d2c81017fc5ef57183cb4d0`
|
||||
- tree: `7905896f005159d596e3872d0ee2c3c12061f09a`
|
||||
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1`
|
||||
- Python: `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python`
|
||||
|
||||
검증 종료 시에도 HEAD/tree가 위 identity와 일치하고 `git status --short`는 비어 있었다. `git diff --check <base>`와 `/bin/bash -n .agents/skills/lib.sh` 모두 통과했다. 리뷰 소스 수정, creator 작업 트리 실행, 커밋·push는 하지 않았다.
|
||||
|
||||
## 변경 검토
|
||||
|
||||
### L1: 조상 디렉터리 이름 때문에 mock이 가려지는 문제
|
||||
|
||||
`.agents/skills/lib.sh:113–124`는 일반 `*-shim` 제외를 디렉터리 자체의 접미사로 제한한다. `:132`에서 PATH 디렉터리를 검사하고, `:134–136`에서는 심볼릭 링크 해석 후 후보 파일의 **dirname**을 검사한다. 따라서 파일 이름 `herdr` 때문에 접미사 검사가 무력화되지 않고, wrapper를 가리키는 링크도 기존처럼 제외된다. 생성 shim의 `:172–176`도 일반 접미사 조건을 같은 방향으로 좁힌다.
|
||||
|
||||
검증 트리의 base와 head에서 해당 함수 본문만 추출해 `/bin/bash`로 실행했다. PATH 후보는 모두 `/tmp`에 만든 디코이이며 실제 herdr는 후보에 없었다.
|
||||
|
||||
| 후보 위치 | base 결과 | head 결과 |
|
||||
|---|---|---|
|
||||
| `pytest-x-shim/case/bin/herdr` | 두 해석기 모두 후보를 건너뛰고 다음 디코이 선택 | 두 해석기 모두 첫 후보 선택 |
|
||||
| `herdr-shim/herdr` | 두 해석기 모두 제외 | 두 해석기 모두 제외 |
|
||||
| `.mam/shim/herdr` | 두 해석기 모두 제외 | 두 해석기 모두 제외 |
|
||||
| `multi-agent-herdr-shim/herdr` | 두 해석기 모두 제외 | 두 해석기 모두 제외 |
|
||||
| `.agents/skills/.bin/herdr` | lib 해석기는 제외; 생성 shim은 선택 | 동일: lib 해석기는 제외; 생성 shim은 선택 |
|
||||
|
||||
추가로 lib 해석기에서 일반 `front/herdr` 심볼릭 링크가 `wrapped-shim/herdr` 또는 `.agents/skills/.bin/herdr`를 가리키면 제외됨을 확인했다. 링크 대상이 `parent-shim/bin/herdr`이면 허용됐다. 조상 이름과 wrapper 자체의 이름을 구분하려는 변경에 부합한다.
|
||||
|
||||
### L2: pane 라우팅 환경 격리
|
||||
|
||||
`tests/conftest.py:290–303`에서 기존 세션 이름·workspace·config 격리에 socket/pane/tab 삭제가 추가됐다. `tests/test_herdr_real_server_leak.py:73–87`은 안전한 가짜 socket 값과 pane 변수를 먼저 주입하고 `mam_sandbox` 이후 남지 않는지 검증하며 통과했다. 바깥 실행 환경을 미리 정리했더라도 이 시험은 자체적으로 오염 환경을 다시 만들어 검사한다.
|
||||
|
||||
브리프에 기록된 실제 herdr의 socket 우선순위 실측과 일치하는 수정이다. 이번 리뷰에서는 운영 서버 접촉을 금지한 조건에 따라 실제 서버 라우팅을 재현하지 않았다. 따라서 socket 우선순위의 실서버 증거는 브리프의 실측이고, 본 리뷰의 독립 증거는 fixture 삭제 동작 및 디코이 호출 경로 검증이다.
|
||||
|
||||
### mam_sandbox 밖의 경로와 HERDR_SESSION
|
||||
|
||||
삭제 범위는 `mam_sandbox`를 사용하는 시험에 한정된다. 검토한 비-sandbox 경로에서 이번 사고와 같은 추가 실제 서버 변경 경로는 찾지 못했다.
|
||||
|
||||
- `tests/test_issue50_dispatch_herdr.py:73–85`의 실제 서버용 Box 환경은 모든 `HERDR_*`를 제거하고 일회용 HOME/서버를 지정한다.
|
||||
- `tests/test_issue45_restore.py:41–57`의 Rig는 socket/pane/tab/workspace 및 `HERDR_SESSION`, config 관련 값을 제거한다. 이 실서버 모듈들은 이번 리뷰에서 실행하지 않고 코드만 확인했다.
|
||||
- `tests/test_issue17_item4_decisions.py:224–233`은 별도 환경 구성에서 `HERDR_*` 전체를 제거한다. 다른 비-sandbox 단위 시험은 in-process stub, 추출한 shell 함수, 또는 명시적인 mock을 사용하는 경로인지 확인했다.
|
||||
- `mam_sandbox`는 `HERDR_SESSION` 자체를 삭제하지 않는다. 해당 값은 `lib.sh:1425–1427`의 caller 식별 및 `lib_py/pane_lock.py:45–65`의 lock 이름에 사용될 수 있다. lock 경로는 sandbox HOME 아래이며, socket·config 경로가 제거된 상태에서 이 변수만으로 운영 서버에 접속하는 재현은 얻지 않았다. 따라서 이를 이번 수정의 차단 결함으로 확대하지 않는다. 향후 공통 환경 정리 시 함께 다룰 수 있는 격리 보강 항목이다.
|
||||
|
||||
## 공유 생성 shim의 영향
|
||||
|
||||
이번 변경은 생성 heredoc의 한 조건을 의도적으로 바꾼다. `_init_herdr_isolation`은 `lib.sh:149–157`에서 제어 루트 아래 임시 shim을 만들고 `:1389–1390`에서 rename으로 교체한다. 별도 wrapper 내용 변경 외의 프로토콜 변경은 없다.
|
||||
|
||||
`test_issue23_24_startup_path.py:295–315`의 기준을 `525df2b`로 이동한 것은 이 의도적인 heredoc 변경을 반영하며 해당 테스트가 실제로 통과했다. 다만 이 테스트는 head와 그 새 기준의 동일성을 확인할 뿐, 현재 실행 중인 모든 체크아웃이 새 버전을 사용한다는 증명은 아니다. 오래된 코드와 새 코드가 동일 제어 루트를 공유하며 shim을 다시 생성하면 마지막 writer의 버전이 적용된다. 배포 시 같은 제어 루트의 생성 주체를 같은 버전으로 맞추는 것이 필요하다. 리뷰 중에는 운영 제어 루트의 shim을 읽거나 갱신하지 않았다.
|
||||
|
||||
## 비차단 기존 차이: 생성 shim의 .bin 제외 누락
|
||||
|
||||
`lib.sh:115`의 lib 해석기는 `.agents/skills/.bin`을 제외하지만, `:176`의 생성 shim 해석기는 그 별도 규칙을 가지고 있지 않다. 위 디코이 탐침에서 base와 head 모두 동일하게 확인했다. 그러므로 **두 해석기가 모든 wrapper 경로에 대해 동일한 제외 규칙을 갖는다**고 결론 내릴 수는 없다.
|
||||
|
||||
재현: `.agents/skills/.bin/herdr` 디코이를 PATH 첫 번째에, 다른 디렉터리의 디코이를 그 다음에 놓고 각 해석기를 호출한다. lib는 두 번째를 반환하고 생성 shim은 첫 번째를 반환한다. 원하는 완전한 일관성 기준에서는 생성 shim도 첫 번째를 제외해야 한다. 별도 보강 시 두 해석기의 명시적 wrapper 경로 규칙을 맞추고 이 경우를 회귀 테스트에 추가하는 방향이 적절하다. base부터 있던 차이이고 이번 접미사 축소로 새로 생긴 동작이 아니므로 본 변경의 PASS를 막지 않는다.
|
||||
|
||||
## 실행 결과와 재현
|
||||
|
||||
안전 launcher: `/tmp/efb52908-review/run.py`
|
||||
|
||||
```text
|
||||
env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u WORKSPACE_ROOT -u MAM_ENV_FILE <Python> /tmp/efb52908-review/run.py tests/test_herdr_real_server_leak.py tests/test_herdr_shim_contract.py tests/test_issue23_24_startup_path.py tests/test_issue9_server_env.py -k 'not test_h10_real_herdr_schema_match'
|
||||
```
|
||||
|
||||
- 결과: **105 passed, 1 deselected in 177.07s**, exit 0.
|
||||
- 실제 설치 herdr의 schema를 조회하는 `test_h10_real_herdr_schema_match`만 명시적으로 제외했다. 전체 프로젝트 스위트는 실행하지 않았다.
|
||||
- 로그: `/tmp/efb52908-review/tests.log`
|
||||
- basetemp: `/tmp/efb52908-bbxxt8p8/bt` (이름에 shim 없음).
|
||||
- launcher는 추가로 모든 상속 `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*` 및 제어/에이전트 경로 변수를 제거하고 일회용 HOME, 지정 Python, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`를 사용했다.
|
||||
- fallback PATH에 호출을 기록하고 exit 97로 종료하는 herdr 디코이를 설치했다. **fallback 호출 기록 없음.** 테스트별 mock이 가려져 설치 herdr까지 내려가는 상황을 허용하지 않았다.
|
||||
- broker는 기존 suite의 로컬 fixture를 사용했다. 운영 `.mam.env`를 export/출력하지 않았고 실제 사용자 홈·서버·모니터를 변경하지 않았다. agent-home 가드 오탐도 없었다.
|
||||
- 독립 경로 탐침: `/tmp/efb52908-review/probe.py`, 로그 `/tmp/efb52908-review/probe.log`. 검증 cwd에서 같은 `env -u ... <Python>`으로 실행 가능하다. 5개 경로 × 2개 해석기의 base/head 관측과 3개 링크 사례를 기록했다. 위 기존 .bin 차이를 제외한 기대 조건 assertion은 모두 통과했다. 실제 herdr를 실행하지 않았다.
|
||||
|
||||
소스 변경 없이 지정 범위의 검토를 마쳤으며 재계획이 필요한 결함은 발견하지 않았다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T001819Z-131fa6",
|
||||
"base_sha": "8b0b4a07dc76a4915895bdde6170dc8eed9b5f74",
|
||||
"head_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"tree_sha": "7905896f005159d596e3872d0ee2c3c12061f09a",
|
||||
"review_commit": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"reviewer": "reviewer-agy-01",
|
||||
"job_id": "a86376f9",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T00:57:46Z"
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# Code Review Report: `fix/test-herdr-leak`
|
||||
|
||||
- **Job ID**: `a86376f9`
|
||||
- **Reviewer**: `agy` (Session: `herdr:reviewer-agy-01`, Role: `Reviewer`)
|
||||
- **Date**: 2026-10-13
|
||||
- **Target Branch**: `fix/test-herdr-leak`
|
||||
- **Base Commit**: `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74` (`main`)
|
||||
- **Head Commit**: `325b056057fcdd717d2c81017fc5ef57183cb4d0`
|
||||
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/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 code review evaluated the fixes in `fix/test-herdr-leak` (`8b0b4a0..325b056`, 2 commits, 4 files), resolving an incident where test suites executed from an agent pane with `--basetemp .../pytest-pr56-shim` inadvertently created 7 workspaces on the pane's real herdr server (`multi-agent-mux`).
|
||||
|
||||
### Root Causes Addressed
|
||||
1. **Overspecified `-shim` Pattern Match**:
|
||||
In [`lib.sh:_is_shim_path`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/.agents/skills/lib.sh#L53) and the generated shim heredoc, the pattern `*-shim/*` matched *any* path having `-shim/` anywhere in its ancestor hierarchy. When pytest was invoked with `--basetemp .../pytest-pr56-shim`, any mock herdr binary placed under `$basetemp/.../bin/herdr` was misclassified as a shim and skipped. The real-herdr resolver continued down `$PATH` until finding system `/opt/homebrew/bin/herdr`.
|
||||
2. **Host Socket Leaked Through `mam_sandbox`**:
|
||||
`mam_sandbox` previously unset `HERDR_SESSION_NAME`, `HERDR_SERVER_NAME`, and `HERDR_WORKSPACE_ID`, but left `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID` untouched. In `herdr` 0.9.3, `HERDR_SOCKET_PATH` overrides `$HOME`, so any unintended call to the real herdr binary connected directly to the host pane's herdr daemon socket.
|
||||
|
||||
### The Fix
|
||||
1. In [`lib.sh`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/.agents/skills/lib.sh):
|
||||
- Refined `_is_shim_path()`: removed `*-shim/*` from substring globbing and added `case "${1%/}" in *-shim) return 0 ;; esac` so only directories whose leaf name matches `*-shim` are considered shims.
|
||||
- Passed `$(dirname "$cand")` into `_is_shim_path` in `_resolve_real_herdr_path`.
|
||||
- Updated generated shim heredoc: replaced `[[ "$dir" != *"-shim"* ]]` with `[[ "${dir%/}" != *-shim ]]`.
|
||||
2. In [`tests/conftest.py:mam_sandbox`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/tests/conftest.py#L293):
|
||||
- Explicitly strips `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID`.
|
||||
3. In [`tests/test_issue23_24_startup_path.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/tests/test_issue23_24_startup_path.py):
|
||||
- Updated the generated shim byte-identity pin to base commit `525df2b`.
|
||||
4. In [`tests/test_herdr_real_server_leak.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/tests/test_herdr_real_server_leak.py):
|
||||
- Added unit and regression tests reproducing both leak vectors and verifying proper resolution.
|
||||
|
||||
**Verdict: PASS**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Evaluation
|
||||
|
||||
### 2.1 Diagnosis & Resolution Accuracy
|
||||
- **Pattern Match Tightening**:
|
||||
Testing with mock binaries located under `/path/to/pytest-x-shim/case0/bin/herdr` confirms that:
|
||||
- `$(dirname "$cand")` is `/path/to/pytest-x-shim/case0/bin`.
|
||||
- Leaf directory name is `bin`, which does not match `*-shim`.
|
||||
- The mock binary is accepted and executed; system `herdr` is never reached.
|
||||
- Dedicated wrapper directories whose leaf name is `*-shim` (e.g. `tmp / "herdr-shim" / "herdr"`) are still properly skipped.
|
||||
- **Routing Variable Cleansing**:
|
||||
Unsetting `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID` eliminates the socket bypass vector in herdr 0.9.3. If a test ever fails to mock herdr, the real binary fails with `server_not_running` rather than attaching to the active multi-agent session.
|
||||
|
||||
### 2.2 Secondary Leak Paths & Environmental Isolation
|
||||
- All known `HERDR_*` variables are accounted for in `mam_sandbox`:
|
||||
- `HERDR_SESSION_NAME`, `HERDR_SERVER_NAME`, `HERDR_WORKSPACE_ID`
|
||||
- `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`
|
||||
- `HERDR_CONFIG_PATH`, `XDG_CONFIG_HOME`
|
||||
- Non-sandbox integration tests (such as [`tests/test_issue50_dispatch_herdr.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/tests/test_issue50_dispatch_herdr.py)) explicitly scrub all environment variables starting with `HERDR_` and isolate `$HOME` under `/tmp`.
|
||||
|
||||
### 2.3 Exclusion of Real Shim Directories
|
||||
The narrowed `_is_shim_path` pattern continues to exclude all designated shim locations:
|
||||
- `*/.mam/shim/*`: matched by `*"/.mam/shim/"*`.
|
||||
- `multi-agent-herdr-shim`: matched by `*"$_HERDR_SHIM_DIR_PATTERN"*`.
|
||||
- `.agents/skills/.bin`: matched by `*"$_HERDR_SKILLS_BIN_PATTERN"/*`.
|
||||
- Arbitrary test wrapper directories ending in `-shim`: matched by `${1%/}` -> `*-shim`.
|
||||
|
||||
### 2.4 Control Plane Impact
|
||||
The generated shim in `.mam/shim/herdr` is modified only to use `[[ "${dir%/}" != *-shim ]]`. The byte-identity assertion in [`test_issue23_24_startup_path.py::test_s11`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T001819Z-131fa6/1/tests/test_issue23_24_startup_path.py#L200) was updated to compare against commit `525df2b`, consistent with historical updates for intentional shim modifications (#45).
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Verification Results
|
||||
|
||||
All tests executed with sanitized environment variables (`env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u WORKSPACE_ROOT -u MAM_ENV_FILE`):
|
||||
|
||||
| Test Suite / Target | Result | Execution Time |
|
||||
|---|---|---|
|
||||
| `tests/test_herdr_real_server_leak.py` (New regression suite) | **3 Passed** | 1.39s |
|
||||
| `tests/test_issue23_24_startup_path.py` (Full suite, 70 tests) | **70 Passed** | 141.34s |
|
||||
| `tests/test_herdr_shim_contract.py` (Full suite, 18 tests) | **18 Passed** | 27.48s |
|
||||
|
||||
- `test_l1_ancestor_named_shim_does_not_hide_the_mock`: Verifies mock binary under `...-shim/.../bin` is used, decoy is not touched.
|
||||
- `test_l1_a_directory_named_shim_is_still_skipped`: Verifies wrapper directories named `*-shim` remain excluded.
|
||||
- `test_l2_sandbox_drops_the_pane_routing`: Verifies `mam_sandbox` purges all pane routing variables.
|
||||
|
||||
---
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T010208Z-f90dd4",
|
||||
"base_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"head_sha": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"tree_sha": "f32cabd3a6c8d775c9223509967f1a810f0cf827",
|
||||
"review_commit": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"reviewer": "reviewer-agy-01",
|
||||
"job_id": "e375099a",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T01:21:50Z"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Code Review Report (2nd Round): `fix/test-herdr-leak`
|
||||
|
||||
- **Job ID**: `e375099a`
|
||||
- **Reviewer**: `agy` (Session: `herdr:reviewer-agy-01`, Role: `Reviewer`)
|
||||
- **Date**: 2026-10-13
|
||||
- **Target Branch**: `fix/test-herdr-leak`
|
||||
- **Base Commit**: `325b056057fcdd717d2c81017fc5ef57183cb4d0` (1st round review head)
|
||||
- **Head Commit**: `db7f4cab9c60ff5db009cf3fc625e9a86a465ef6` (Fix commit)
|
||||
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/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 2nd-round review evaluated commit `db7f4cab9c60ff5db009cf3fc625e9a86a465ef6`, addressing non-blocking feedback from 1st-round reviews (claude `63919c5d` and grok `191f0ec3`):
|
||||
- **Finding**: While `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID` were stripped in `mam_sandbox`, `HERDR_SESSION` was still inherited from the ambient host environment.
|
||||
- **Impact**: Although `HERDR_SESSION` on its own resolves under `$HOME/.config/herdr/sessions/<session>` (so sandboxing `$HOME` prevented socket file leaks), ambient-server helpers like [`_herdr_caller_server`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1/.agents/skills/lib.sh#L1453) and [`pane_lock.caller_server()`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1/.agents/skills/lib_py/pane_lock.py#L671) read `HERDR_SESSION` first. Consequently, tests branched on whether they were executed from within an agent pane or outside a pane.
|
||||
- **The Fix**:
|
||||
1. [`tests/conftest.py:mam_sandbox`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1/tests/conftest.py#L296): added `monkeypatch.delenv("HERDR_SESSION", raising=False)`.
|
||||
2. [`tests/test_herdr_real_server_leak.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T010208Z-f90dd4/1/tests/test_herdr_real_server_leak.py#L64): added `"HERDR_SESSION": "real"` to `PANE_VARS`, covered by `test_l2_sandbox_drops_the_pane_routing`.
|
||||
|
||||
**Verdict: PASS**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Technical Evaluation
|
||||
|
||||
### 2.1 Environmental Isolation & Branching Consistency
|
||||
- In `lib_py/pane_lock.py:caller_server()`:
|
||||
```python
|
||||
def caller_server(env: Mapping[str, str]) -> str:
|
||||
if env.get("HERDR_SESSION"):
|
||||
return env["HERDR_SESSION"]
|
||||
```
|
||||
And in `lib.sh`:
|
||||
`HERDR_SESSION` indicates that the current process runs inside an active herdr pane.
|
||||
By stripping `HERDR_SESSION` in `mam_sandbox`, every test begins with a clean non-pane baseline environment. Test behavior is now strictly reproducible regardless of whether the test runner runs inside an interactive terminal, CI, or an autonomous agent herdr session.
|
||||
|
||||
### 2.2 Test Compatibility
|
||||
- Tests that explicitly test `HERDR_SESSION` configuration (e.g. `test_issue50_dispatch_herdr.py:769`, `test_issue45_restore.py`, `test_tier1_unit.py`) set the environment variable directly within the test body or pass it via `env=...` in subprocess calls. Because test bodies execute after fixture setup, their explicit settings take precedence and function properly.
|
||||
- All 6 unit tests in `tests/test_issue50_pane_lock.py` (`test_p1` to `test_p5`) and contract tests in `tests/test_herdr_shim_contract.py` pass cleanly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Verification Results
|
||||
|
||||
All tests executed with sanitized environment variables (`env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE`):
|
||||
|
||||
| Test Suite / Target | Result | Execution Time |
|
||||
|---|---|---|
|
||||
| `tests/test_herdr_real_server_leak.py` | **3 Passed** | 1.44s |
|
||||
| `tests/test_herdr_shim_contract.py:test_h26` | **2 Passed** | 3.98s |
|
||||
| `tests/test_issue50_pane_lock.py:test_p*` | **6 Passed** | 4.57s |
|
||||
|
||||
- `test_l2_sandbox_drops_the_pane_routing`: Verifies that `HERDR_SESSION` is stripped by `mam_sandbox` alongside `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, and `HERDR_SESSION_NAME`.
|
||||
|
||||
---
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T001819Z-131fa6",
|
||||
"base_sha": "8b0b4a07dc76a4915895bdde6170dc8eed9b5f74",
|
||||
"head_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"tree_sha": "7905896f005159d596e3872d0ee2c3c12061f09a",
|
||||
"review_commit": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "191f0ec3",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T00:57:46Z"
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# Review 191f0ec3 — a test run from a pane no longer opens workspaces on that pane's server
|
||||
|
||||
Head `325b056057fcdd717d2c81017fc5ef57183cb4d0` (tree `7905896f005159d596e3872d0ee2c3c12061f09a`) is two commits on base `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`: `525df2b` (the leak fix) and `325b056` (the generated-shim pin moves to `525df2b`). Four files, +106/−5. `git diff --check` is clean. Snapshot porcelain empty. Control-plane main is still `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`, porcelain empty.
|
||||
|
||||
Both measured causes are right, and the fix matches them.
|
||||
|
||||
The suite that left seven workspaces on `multi-agent-mux` ran with `--basetemp .../pytest-pr56-shim`. `mock_herdr` puts its `bin/herdr` under that basetemp. Both resolvers used to skip any PATH directory whose path contained `-shim` anywhere, so they walked past the mock and executed `/opt/homebrew/bin/herdr`. The real binary follows `HERDR_SOCKET_PATH` ahead of `HOME`, and `mam_sandbox` left the pane's socket in the environment.
|
||||
|
||||
`_is_shim_path` (`lib.sh:113-125`) still matches `/.mam/shim/`, `/multi-agent-herdr-shim/`, and `/.agents/skills/.bin`. The substring `*-shim/*` is gone. A second `case` matches only when the path with one trailing slash stripped is itself `*-shim`. `_resolve_real_herdr_path` (`:127-143`) skips a PATH directory on that test, then skips the candidate when `_is_shim_path` matches `dirname` of the canonical file (`:136`). It still prints the PATH entry, `$dir/herdr`. The generated shim's `_resolve_real_herdr` (`:172-186`) now requires `${dir%/}` to fail the pattern `*-shim`, and it still rejects a directory containing `.mam/shim`.
|
||||
|
||||
`mam_sandbox` deletes `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, and `HERDR_TAB_ID` (`tests/conftest.py:296-298`).
|
||||
|
||||
## Wrapper directories
|
||||
|
||||
A decoy-only probe sourced this snapshot's `lib.sh` with `WORKSPACE_ROOT=/tmp/m191ws`. PATH was the case directory, a stand-in, `/usr/bin`, and `/bin`. The real binary was not on it.
|
||||
|
||||
| PATH directory | `_resolve_real_herdr_path` | generated shim |
|
||||
|---|---|---|
|
||||
| `.../pytest-x-shim/case0/bin` | that mock | that mock |
|
||||
| a directory whose own name is `herdr-shim` | stand-in | stand-in |
|
||||
| `.../multi-agent-herdr-shim/srv` | stand-in | that directory |
|
||||
| `.../.agents/skills/.bin` | stand-in | that directory |
|
||||
| `.../.mam/shim/extra` | stand-in | stand-in |
|
||||
|
||||
An ancestor named `pytest-x-shim` above `bin/` is selected by both resolvers. A directory that is itself `*-shim` is skipped by both. `.mam/shim` and a child of it are skipped by both. L1 and L1b assert the same two outcomes inside the suite, with a decoy behind the mock, and both passed.
|
||||
|
||||
`lib.sh` still excludes `/multi-agent-herdr-shim/` and `/.agents/skills/.bin`. The generated shim excludes a PATH entry whose own name ends in `-shim`, so the directory `multi-agent-herdr-shim` itself is skipped by both. A child of that directory, whose own name does not end in `-shim`, is excluded only by `_resolve_real_herdr_path`. `README.md:102` and `README.ko.md:84` still describe `/tmp/multi-agent-herdr-shim/<HERDR_SERVER_NAME>/herdr`. No script in this tree creates that path; `_init_herdr_isolation` writes `$WORKSPACE_ROOT/.mam/shim/herdr`. The `.agents/skills/.bin` gap on the generated shim is older than this commit: the previous generated check was the substring `*"-shim"*`, and `.bin` never matched it. Nothing in `lib.sh` prepends `.agents/skills/.bin` onto PATH.
|
||||
|
||||
## What still reaches the real binary
|
||||
|
||||
A direct call of `mam_sandbox`, with probe values set first, left `HERDR_SESSION` and `HERDR_ENV` set and removed `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_SESSION_NAME`, `HERDR_WORKSPACE_ID`, and `HERDR_CONFIG_PATH`. `HOME` was the sandbox. L2's `PANE_VARS` (`tests/test_herdr_real_server_leak.py:74-75`) are the socket, the pane and tab ids, the workspace id, and `HERDR_SESSION_NAME`. The test passed. It does not include `HERDR_SESSION`.
|
||||
|
||||
This reviewer process inherited `HERDR_SESSION` from its pane. `lib.sh:1411-1415` records that herdr 0.8.2 left `HERDR_SESSION` unset on a default-server pane and exported the socket to every pane. `docs/contracts/issue-6/implementation_plan.md` records the measurement that the real binary follows `HERDR_SESSION`, `HERDR_SOCKET_PATH`, and `--session`, and that `HERDR_SESSION_NAME` alone reaches the default socket. The calls that created the seven workspaces go through these resolvers, which now return the mock, so they do not reach the binary that reads `HERDR_SESSION`. A later call that misses the mock still inherits `HERDR_SESSION`.
|
||||
|
||||
The suite's intentional real-server tests already drop it. `tests/test_issue45_restore.py` lists `HERDR_SESSION` in `SCOPE_ENV` and builds the server env from what remains. `tests/test_issue50_dispatch_herdr.py` `Box.env` drops every `HERDR_*` name, and `Box.herdr` passes only `HOME`, `PATH`, and `--session`. `tests/test_herdr_shim_contract.py::test_h10_real_herdr_schema_match` calls `herdr api schema` with the inherited environment and does not use `mam_sandbox`. That test was already in the tree. I did not run it, and I did not run the real binary. `api schema` is a read. The workspace-creating path was the resolver.
|
||||
|
||||
`HERDR_ENV` also survived the fixture call. The measured contract names `HERDR_SESSION`, `HERDR_SOCKET_PATH`, and `--session`. I did not treat `HERDR_ENV` as a second route.
|
||||
|
||||
## Shared control plane
|
||||
|
||||
`test_s11_this_branch_does_not_change_the_generated_shim` (`tests/test_issue23_24_startup_path.py:294-315`) compares the shim heredoc with `git show 525df2b:.agents/skills/lib.sh`, unless `MAM_BASE_SHA` overrides it. It passed, so the heredoc at `325b056` is the heredoc at `525df2b`. The second commit changes the pin and the comment. Sourcing `lib.sh` rewrites `$WORKSPACE_ROOT/.mam/shim/herdr`. This review sourced it with `WORKSPACE_ROOT` pointed at `/tmp/m191ws` and at pytest sandboxes. The narrowing becomes the control plane's shim when this branch is merged and something sources `lib.sh` with that workspace root.
|
||||
|
||||
## What was run
|
||||
|
||||
Control-plane venv, Python 3.9.6, pytest 8.4.2, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp=/tmp/m191bt-leak2` (the path contains neither `shim` nor `mam-review`). Inherited `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `HERDR_SERVER_NAME`, `HERDR_CONFIG_PATH`, `HERDR_ENV`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, and `CODEX_HOME` were unset.
|
||||
|
||||
`tests/test_herdr_real_server_leak.py` and `tests/test_issue23_24_startup_path.py::test_s11_this_branch_does_not_change_the_generated_shim`: 4 passed in 1.50s. The session guards that travel with that run completed. The #55 agent-home guard did not fire.
|
||||
|
||||
User `~/.config/herdr/config.toml` mtime stayed `1784456394`. Snapshot porcelain stayed empty after the run.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261013T010208Z-f90dd4",
|
||||
"base_sha": "325b056057fcdd717d2c81017fc5ef57183cb4d0",
|
||||
"head_sha": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"tree_sha": "f32cabd3a6c8d775c9223509967f1a810f0cf827",
|
||||
"review_commit": "db7f4cab9c60ff5db009cf3fc625e9a86a465ef6",
|
||||
"reviewer": "reviewer-grok-01",
|
||||
"job_id": "9cd9b3f0",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-13T01:21:50Z"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Review 9cd9b3f0 — the sandbox also drops HERDR_SESSION
|
||||
|
||||
Head `db7f4cab9c60ff5db009cf3fc625e9a86a465ef6` (tree `f32cabd3a6c8d775c9223509967f1a810f0cf827`) is the one commit `db7f4ca test(sandbox): mam_sandbox also drops HERDR_SESSION` on base `325b056057fcdd717d2c81017fc5ef57183cb4d0`. Two files, +5/−1. `git diff --check` is clean. Snapshot porcelain empty. Control-plane main is still `8b0b4a07dc76a4915895bdde6170dc8eed9b5f74`, porcelain empty.
|
||||
|
||||
`mam_sandbox` deletes `HERDR_SESSION` next to `HERDR_SOCKET_PATH` (`tests/conftest.py:298-299`). L2's pane model now includes `HERDR_SESSION=real` (`tests/test_herdr_real_server_leak.py:73-76`) and asserts that name is gone with the others (`:86-88`).
|
||||
|
||||
## What the variable does inside a sandbox
|
||||
|
||||
The herdr binary treats `HERDR_SESSION` as a session name: the string beside it is `session name cannot be empty`. A named session's socket is `$HOME/.config/herdr/sessions/<name>/herdr.sock`. `mam_sandbox` sets `HOME` to the sandbox, so that name resolves inside the sandbox. The route that reached the caller's real server was `HERDR_SOCKET_PATH`, which the previous commit already deletes.
|
||||
|
||||
`_herdr_caller_server` (`lib.sh:1424-1427`), `pane_lock.caller_server` (`lib_py/pane_lock.py:44-50`), and `watcher.caller_server` (`lib_py/loop/watcher.py:50-66`) return `HERDR_SESSION` whenever it is set, ahead of the socket. A test run inside a pane therefore took the in-pane branch, and the same test outside a pane took the other. Dropping the variable makes those helpers see a process outside a pane unless the test supplies the name itself.
|
||||
|
||||
## Tests that supply the name
|
||||
|
||||
No test calls `monkeypatch.setenv("HERDR_SESSION")`. The tests that need a value build the child environment themselves:
|
||||
|
||||
- `tests/test_issue23_24_startup_path.py` `_run_lib` (`:36-40`) pops `HERDR_SESSION` from the inherited environment and then applies the dict the test passes. `test_s1g` passes `HERDR_SESSION=from-herdr` and expects `caller=from-herdr`.
|
||||
- `tests/test_issue34_job_tree.py` `_run_lib` (`:590-591`) drops every `HERDR_*` name and then applies `SAME_SERVER_PANE`, which contains `HERDR_SESSION=multi-agent-mux`.
|
||||
- `tests/test_issue50_pane_lock.py` passes an explicit mapping into `lock_path` and into the `pane_lock path` subprocess. `test_p1b` builds that subprocess environment from the keys that do not start with `HERDR_`, then adds the socket it wants.
|
||||
|
||||
A direct call of `mam_sandbox` with `HERDR_SESSION=probe-session` left the name unset. A `setenv` after that call left `HERDR_SESSION=set-by-test`. The fixture setup runs before the test body, so a test that sets the name for itself keeps it. The one `setenv` of a `HERDR_` name in a test body is `HERDR_SERVER_NAME` in `tests/test_tier3_integration.py:31`, and that line runs after the fixture.
|
||||
|
||||
## What the sandbox still leaves
|
||||
|
||||
`HERDR_ENV` stays. In this pane its value is `1`. The binary's text says `Requires HERDR_ENV=1` beside the inception check, so this is a feature flag. `HERDR_BIN_PATH` stays, and its value names the herdr binary. `HERDR_CLIENT_SOCKET_PATH` sits beside `HERDR_SOCKET_PATH` in the binary's pane-export cluster. This process does not have that name, and the sandbox does not delete it.
|
||||
|
||||
## What was run
|
||||
|
||||
Control-plane venv, Python 3.9.6, pytest 8.4.2, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, `--basetemp=/tmp/m9cdbt`. Inherited `HERDR_SOCKET_PATH`, `HERDR_PANE_ID`, `HERDR_TAB_ID`, `HERDR_WORKSPACE_ID`, `HERDR_SESSION_NAME`, `HERDR_SESSION`, `HERDR_SERVER_NAME`, `HERDR_CONFIG_PATH`, `HERDR_ENV`, `HERDR_BIN_PATH`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `HOME_DIR`, `CLAUDE_PROJECT_DIR`, and `CODEX_HOME` were unset. The real herdr binary was not run.
|
||||
|
||||
`tests/test_herdr_real_server_leak.py`, `tests/test_issue23_24_startup_path.py`, and `tests/test_issue50_pane_lock.py`: 84 passed in 195.84s. That is the same 84 the orchestrator reported. The session guards on that run completed. The #55 agent-home guard did not fire.
|
||||
|
||||
User `~/.config/herdr/config.toml` mtime stayed `1784456394`. Snapshot porcelain stayed empty after the run.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user