Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.md
T
GodopuandClaude Fable 5.1 c3e6a7e96d release(v4.7.3): bump framework and eight skills to 4.7.3 in lockstep; archive PR #37 merge-side reviews
Issue #36: delegated prompts travel by file, a delegation that fails ends
the loop with rc 1. VERSIONS.md carries the entry; the merge-side reports
(round 1 @59be1c8 and round 2 @708262a with identity sidecars, base
59be1c8 -> head 708262a, evidence review-tree) are archived under
.agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 18:10:28 +09:00

98 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔍 Review Report — PR #37 델타 리뷰 2회차 (job `1a9fbbc4`)
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-24
- **대상 델타**: `59be1c8..708262a` (1커밋, 3파일 `+63/−7`, `git diff --check` 클린), base `main 1debc7d` (v4.7.2).
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20260924T085626Z-b8987e/1`. **identity 확인**: `HEAD = 708262a5…1e30`, `HEAD^{tree} = d601c2d0…5b96` — 브리프와 일치.
- **1회차 내 판정**: PASS + H1~H5 (job `4ab12ae2`). 이번에는 H1(러너 쓰기 실패)·H3(빈 파일)·S9 이식성의 정정을 본다.
- **안전 규칙 준수**: 검증 경로·소스 워크트리·본 체크아웃(`main 1debc7d`) 모두 수정·커밋·checkout 없음. 러너 실험은 in-process 스텁, 래퍼는 `--dry-run`+`--agent human`+도달 불가 브로커, 되돌린 사본은 scratchpad. 실제 herdr·세션·`.mam`·데몬 조작 없음, push·merge·PR 상태 변경 없음.
## 결론 요약
**세 정정 모두 지적을 닫고, 1회차 판정을 뒤집을 요소는 없다.** 내가 1회차에서 H1 을 재현했던 바로 그 스크립트를 정정된 러너에 다시 돌려 "실패 제출·부분 파일 없음·래퍼 미실행"을 확인했고, 정상·exec 실패·쓰기 실패 세 경로에서 파일이 **정확히 한 번**만 지워지는 것도 확인했다. 비차단 관찰 1건(I1).
## 1. R1/H1 — 러너의 쓰기 실패 (항목 1)
`runner.py:472-491`: `mkdir`·`NamedTemporaryFile`·`write` 가 하나의 `try` 로 들어가고, `prompt_file` 은 `""` 로 초기화된 뒤 파일이 생기는 즉시(`write` 이전) 이름을 받는다. `except OSError` 는 이름이 있으면 지우고 `CompletedProcess(…, 126, "", "cannot write the prompt file under <mam_dir>: <err> (prompt N bytes)")` 를 돌려준다. exec 용 `try/finally`(`:504-516`)는 그 뒤에 따로 있어 두 블록이 겹치지 않는다.
### 실측 (검증 경로 러너 in-process)
| 경로 | 결과 |
|---|---|
| **쓰기 실패**(1회차 H1 재현 스크립트 그대로: `write` 가 `OSError(28)`) | `delegate_job` 이 **None** 을 반환(예외 없음), 로그 `cannot write the prompt file under …: [Errno 28] No space left on device (prompt 5 bytes)`, `.mam` 잔존 **0**, 래퍼 호출 **0** |
| 정상 | 래퍼 호출 시점에 파일 존재·내용 `'hello'`·권한 **0600**, `os.unlink` 호출 **1회**, 잔존 0 |
| exec 실패(`subprocess.run` 이 `OSError(2)`) | rc 126 경로, `os.unlink` 호출 **1회**, 잔존 0, 반환 None |
- 반환하는 `CompletedProcess` 는 exec 실패와 같은 rc 126 이라 기존 처리(`submit failed (exit 126)` 로그, busy 재시도 없음, id 없음 → `failed_submit_jobs` 미등록·None 반환 → 호출자 `return 1`)에 그대로 합류한다. S4c 가 `submit failed (exit 126)`·`cannot write the prompt file`·`Traceback` 부재·래퍼 미실행·잔존 없음을 한꺼번에 단언한다.
- `NamedTemporaryFile` 생성 자체가 실패하면 `prompt_file` 이 빈 채로 `except` 에 들어가 `unlink` 를 건너뛴다 — 지울 것이 없으니 맞다. `mkdir` 실패도 같은 블록이다.
## 2. H3 — 빈 `--prompt-file` 거부 (항목 2)
`multi-agent-mux-delegate-job:193` 의 `[[ -s "$PROMPT_FILE" ]]` 는 **래퍼 `submit` 의 입력**에만 걸린다. 정당한 빈 프롬프트 사용처를 막는지 실측:
| 경로 | 결과 |
|---|---|
| 래퍼 `submit --prompt-file empty.md` | `submit: --prompt-file <절대경로> is empty`, rc 1 (S1e) |
| 래퍼 `submit --prompt-file newline.md`(개행 1바이트) | 등록됨 — `-s` 는 바이트 크기 기준이라 공백만 있는 파일은 통과한다(I1) |
| 래퍼 `submit --prompt ""` | 종전대로 `submit requires --prompt or --prompt-file` 거부 — 텍스트 형식과 대칭 |
| `registry.py register --prompt-file empty.md`(직접) | 등록됨(rc 0) — 등록부는 그대로 |
| loop 형 회차의 `update --prompt-file "$job_dir/prompt.md"` | `cmd_submit` 을 거치지 않으므로 검사 밖(변경 없음) |
즉 새 검사는 "빈 텍스트를 거부하던" 기존 규칙을 파일 형식으로 확장한 것이고, 등록부·loop 회차 경로는 건드리지 않는다. 막히는 정당한 사용처는 없다.
## 3. R2/N1 — S9 의 macOS 이식성
`sys.platform == "linux"` 일 때만 `File too large` 를 단언하고, rc≠0·`prompt-shloop-*` 잔존 없음·`delegate_job_safe: cannot write the prompt file` 은 모든 플랫폼에서 단언한다. 1회차에서 내가 하네스를 직접 돌려 확인한 macOS 동작(rc 1·잔존 없음·원인 문구 출력)과 정확히 일치하는 계약이다.
## 4. 테스트 (항목 1·3)
### 4-1. 수정 없이는 실패하는가 — 확인함
검증 경로 사본에서 `runner.py` 와 래퍼만 `59be1c8` 판으로 되돌리고 `-k "s4c or s1e or s9 or s4 or s1"` 을 돌렸다:
```
2 failed, 6 passed, 1 skipped in 3.11s
FAILED test_s4c_a_prompt_file_that_cannot_be_written_is_a_failed_submit ← H1
FAILED test_s1e_an_empty_prompt_file_is_refused ← H3
```
S9 는 되돌린 사본에서도 통과한다 — `run_loop.sh` 쪽 수정은 이미 `59be1c8` 에 있었고 이번 델타는 테스트의 단언만 이식 가능하게 바꿨기 때문이다(macOS 에서 통과 = R2 정정 확인). S1/S1c/S1d/S4/S4b 는 양쪽 모두 통과(불변 조건).
### 4-2. 검증 경로에서의 실행
```
tests/test_issue36_prompt_transport.py tests/test_issue5_phase1_delegation.py tests/test_loop_cli.py
tests/test_issue14_delivery_ack.py tests/test_issue25_plan_reuse.py
→ 127 passed, 1 skipped in 215.94s failed 0 (skip = linux_only S1b)
```
1회차의 유일한 실패였던 S9 가 이 호스트에서 통과하고, 새 파일 24건(23 통과 + Linux 전용 skip 1)과 인접 계약 스위트가 모두 통과한다.
### 4-3. 격리
| 항목 | 결과 |
|---|---|
| 실제 `.mam/jobs` 전후 | **차이 0** |
| `prompt-*` 잔존(실제 `.mam` / 검증 경로 `.mam`) | **0 / 0** |
| 검증 경로 tracked 파일 | 변화 없음(HEAD `708262a`), 본 체크아웃 `main 1debc7d` |
## 5. 병합 차단 여부 (항목 3)
1회차 PASS 를 뒤집을 요소는 없다. 델타는 러너의 `try` 범위 확장(`runner.py:472-491`), 래퍼의 `-s` 한 줄(`:193`), 테스트 3건(S4c·S1e·S9 조건부 단언)뿐이고, 정정 대상만 정확히 실패→통과로 바뀌었다. 버전 락스텝 bump 부재(`4.7.2`, 오케스트레이터가 4.7.3 으로 처리 예정) 외에 **병합을 막을 결함은 없다**. 1회차 H2(loop SKILL.md 의 `MAM_DIFF_MAX_*` 의미)·H4(`$PROMPT` 후행 개행)는 이 델타 범위 밖이라 그대로 열려 있다.
| # | 비차단 관찰 | 근거 |
|---|---|---|
| I1 | `-s` 는 바이트 크기 검사라 개행·공백만 있는 프롬프트 파일은 통과한다. 텍스트 형식(`--prompt ""`)의 거부와 완전히 대칭이려면 `[[ -n "$(tr -d '[:space:]' < "$PROMPT_FILE")" ]]` 류가 필요하지만, 실무에서 그런 입력은 없다 | `multi-agent-mux-delegate-job:193`, 실측 |
## 6. 교차검증 가이드
```bash
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T085626Z-b8987e/1
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # 708262a… / d601c2d…
# H1 재현 스크립트(1회차 §1 의 것)를 정정된 러너에 → returned None, leftover [], wrapper called 0
# 되돌린 사본: runner.py·래퍼를 59be1c8 판으로 → -k "s4c or s1e or s9 or s4 or s1" → 2 failed / 6 passed / 1 skipped
(cd "$RP" && AGENT_PYTHON_BIN="$PY" PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -p no:cacheprovider -q --basetemp=/tmp/bt tests/test_issue36_prompt_transport.py) # 23 passed, 1 skipped
```
[VERDICT: PASS]