Files
multi-agent-mux/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-31730364.md
T
Godopu 5ed39f899b fix(agents): harden shell adapter bridge and address double-check review feedback
- create_session.sh: add explicit case fallback for delegate_agent (R1)
- lib_py.agents: add spawn-spec, resume-spec, exit-key argv CLI subcommands (R2)
- resume_session.sh, stop_session.sh: replace python string interpolation with safe argv subcommands (R2)
- stop_session.sh: dynamically iterate adapter.identity_cache_fields (R3)
- verify_session.py, workspace_uuid.py: remove dead imports (R9/N4)
- tests/test_a4_adapter_contract.py: add CLI bridge subcommand, clean-env PYTHONPATH safety, and fallback contract tests (R12/N1)
- SKILL.md, docs, logs: synchronize IMPROVEMENTS.md, LOG.md, and resolve_session_id wording (R8/R10/R11)
- promote verified peer review reports for cline (e7b9812b) and claude (31730364)
2026-08-17 08:54:57 +09:00

202 lines
12 KiB
Markdown

# 🔎 교차 코드 리뷰 리포트 (3차) — A-4 Phase 2 후속 수정분
- **Job ID**: `31730364`
- **Reviewer**: claude (session: `herdr:canary-projects-multi-agent-mux-creator-claude`)
- **Role**: Reviewer (`MULTI_AGENT_RULES.md` §1 — 저장소 파일 0건 변경)
- **리뷰 대상**: `b4821fa` + `7708d3a` 위의 **작업 트리 미커밋 수정분 12파일** (직전 리뷰 `6360d5e4` 대응분)
- **기준**: HEAD `7708d3a`
---
## 0. 요약
직전 리뷰(`6360d5e4`)의 **유일한 차단 사유 N1 이 해소되었고, 실측으로 확인했습니다.** 부수 지적 N3·N4 도 정리되었습니다. 1차(`ae4f2ef3`)의 차단 사유 R1·R2 는 이번에도 재현 프로브로 재검증했으며 그대로 유지됩니다.
이번 라운드의 검증은 **뮤테이션 테스트**로 수행했습니다 — 신규 가드 3종 각각에 대해 대응하는 생산 코드를 고의로 되돌린 뒤, 그 가드가 실제로 죽는지 격리 클론에서 확인했습니다. 결과:
| 가드 | 대응 뮤테이션 | 결과 |
|---|---|---|
| `test_cli_bridge_subcommands_and_quote_safety` | `__main__.py` 의 3개 서브커맨드 제거 | **FAIL** ✅ 진짜 가드 |
| `test_wait_for_tui_ready_missing_tokens_diagnostic` | `lib.sh` 의 빈 토큰 가드 제거 | **FAIL** ✅ 진짜 가드 |
| `test_delegate_agent_resolution_and_fallback` | `create_session.sh` 의 R1 수정을 원래 결함으로 되돌림 | **PASS****가드 아님** |
즉 **N2 는 형태만 갖춰졌을 뿐 여전히 미해결**입니다. 다만 이는 이미 올바른 생산 코드에 대한 회귀 가드 부재이지 동작 결함이 아니고, 직전 리뷰에서도 비차단으로 분류했던 항목이므로 판정은 유지합니다.
| # | 등급 | 요지 |
|---|---|---|
| **N2** | 🟡 **필수 후속** | `test_delegate_agent_resolution_and_fallback``create_session.sh` 를 실행하지 않고 **테스트 안에 복사한 스니펫**을 실행합니다. R1 수정을 완전히 되돌려도 전 스위트가 녹색 — 뮤테이션으로 증명 |
| N5 | ⚪ | `_MAM_READY_TOKENS_CLAUDE` 중복 존치 (3라운드 연속 비차단) |
| R6·R7 | ⚪ | 두 건의 동작 변경이 여전히 커밋 메시지·`LOG.md` 에 미기록 |
---
## 1. N1 — 해소 확인 ✅
`test_cli_bridge_subcommands_and_quote_safety``env = os.environ.copy()` + `env["PYTHONPATH"]` 를 구성해 3개 `subprocess.run` 전부에 `env=env` 를 넘기도록 수정되었습니다. `test_facts_bridge_eval_contract:73-76` 의 기존 선례를 정확히 따랐습니다.
**실측 — 직전 라운드와 동일 조건에서 대조:**
```
$ env -u PYTHONPATH .venv/bin/python -m pytest tests/test_a4_adapter_contract.py -q
직전: 1 failed, 11 passed (ModuleNotFoundError: No module named 'lib_py')
현재: 12 passed in 0.44s ✅
```
`deploy/gitea-ci.yml``pytest tests/ -q` 가 적색이 되던 원인이 제거되었습니다.
## 2. N3 · N4 — 해소 확인 ✅
- **N3**: `create_session.sh` 의 중복 화이트리스트가 제거되어 preflight `:85` 하나만 남았습니다. (제가 1차 리포트에서 "검증이 없다"고 잘못 쓴 데 대응해 추가되었던 블록입니다.)
- **N4**: `verify_session.py` 에서 `resolve_home` 참조가 **0건**이 되었습니다. 모듈 레벨 import 제거가 안전함도 확인했습니다 — `from lib_py.verify_session import …` 전수 조사 결과 `resolve_home` 을 이 모듈에서 가져다 쓰는 곳은 없습니다.
죽은 import 재스캔 결과, 이번 리팩터가 만든 것은 **전부 정리**되었습니다.
| 파일 | 잔여 | 귀속 |
|---|---|---|
| `verify_session.py` | 0건 ✅ | — |
| `workspace_uuid.py` | 0건 ✅ | — |
| `atomic_yaml.py` | 5건 | 리팩터 이전부터 존재 |
| `agents/__main__.py` | `json` 1건 | 리팩터 이전부터 존재 |
| `agents/base.py` | `json`·`sqlite3`·`List` 3건 | 리팩터 이전부터 존재 |
## 3. R1 · R2 — 재검증 유지 ✅
| 검사 | 결과 |
|---|---|
| R1: 브리지 사용 불가 시 위임 키 | claude→`claude-code`, agy→`antigravity-cli`, hermes→`hermes-agent`, cline→`cline-agent` (4/4) |
| R2: 1차에서 코드 실행에 성공했던 페이로드 재투입 | `/bin/claude --dangerously-skip-permissions --session-id u1` — 실행 흔적 없음 |
| `bash -n` (변경된 셸 5종) | 5/5 OK |
---
## 4. 🟡 N2 (필수 후속) — 위임 폴백 테스트가 자기 자신을 검사함
**위치**: `tests/test_a4_adapter_contract.py:315-347`
추가된 §2 블록은 주석에 `Shell fallback resolution when MAM_DELEGATE_AGENT_KEY is unset (R1 fallback)` 이라 적혀 있으나, 실행 대상이 `create_session.sh` 가 아니라 **테스트 파일 안에 f-string 으로 복사해 둔 `case` 문**입니다.
```python
sh_snippet = f'''
AGENT="{agent}"
...
case "$AGENT" in
claude) delegate_agent="claude-code" ;; # ← 테스트가 스스로 써 넣은 코드
...
'''
res = subprocess.run(["bash", "-c", sh_snippet], ...)
assert res.stdout.strip() == expected_key
```
생산 코드를 한 줄도 읽지 않으므로, 단언하는 것은 "테스트가 방금 작성한 `case` 문이 작성된 대로 동작한다" 뿐입니다.
### 뮤테이션 증명
격리 클론(`git clone --local --no-hardlinks`)에 작업 트리 상태를 복사한 뒤, `create_session.sh:249-259` 의 R1 수정을 **원래 결함 형태로 완전히 되돌렸습니다**.
```bash
- delegate_agent="${MAM_DELEGATE_AGENT_KEY:-}"
- if [ -z "$delegate_agent" ]; then
- case "$AGENT" in
- claude) delegate_agent="claude-code" ;;
- ...
- fi
+ delegate_agent="${MAM_DELEGATE_AGENT_KEY:-antigravity-cli}" # ← 1차에서 차단했던 바로 그 결함
```
결과:
```
baseline (수정 상태) : 12 passed in 0.46s
mutant (결함 복원) : 12 passed in 0.46s ← 아무도 눈치채지 못함
```
즉 지금 R1 수정을 되돌리고 커밋해도 전 스위트가 녹색입니다. 1차에서 차단했던 "claude 세션의 위임 잡이 `antigravity-cli` 로 기록되는" 결함이 그대로 재유입될 수 있습니다.
**직전 라운드보다 나빠진 점**이 하나 있습니다. 이전에는 이 테스트가 단순 중복 단언이라 "가드가 없다"는 사실이 코드만 봐도 드러났지만, 지금은 R1 을 명시적으로 언급하는 주석과 셸 실행이 붙어 **가드가 있는 것처럼 읽힙니다.** 후속 작업자가 이를 근거로 안심할 여지가 생겼습니다.
### 권고
`create_session.sh` 를 실제로 실행하되 브리지만 실패하게 만드는 형태로 교체하십시오. 예:
```python
def test_delegate_agent_fallback_in_create_session(tmp_path):
# PATH 앞단에 실패하는 python 스텁을 놓아 facts 브리지만 죽인다
...
res = subprocess.run(["bash", "-c",
f'cd {ws} && bash {create_sh} --workspace {ws} --agent claude '
f'--role creator --submit-job "x" --dry-run'], ...)
assert "claude-code" in res.stdout # antigravity-cli 가 아님
```
`--dry-run` 경로가 위임 블록에 도달하지 않는다면, 최소한 스크립트 본문에서 해당 `case` 블록을 추출해 실행하는 형태(파일을 읽어 `sed`/`awk` 로 잘라내 `bash -c`)로라도 **생산 파일이 입력에 포함**되어야 합니다.
---
## 5. ⚪ 잔여 (비차단, 판정 무관)
### N5 — `_MAM_READY_TOKENS_CLAUDE` 중복 존치
`lib.sh` 에 여전히 2회 등장합니다(`:63` 정의, `:1735` `handle_startup_dialogs` 소비). `ClaudeAgentAdapter.ready_tokens` 와 동일 문자열을 두 곳이 각자 보유하는 상태로, M7 이 없애려던 이중 진실원입니다. 1·2차에 이어 3라운드 연속 비차단으로 남깁니다 — 값이 갈라지기 전까지는 무해하나, 갈라지면 조용히 어긋납니다.
### R6 · R7 — 동작 변경 미기록
- **R6**: purge 경로 키가 `workspace_key()``realpath` 기준으로 전환 (심볼릭 링크 하위 워크스페이스에서 삭제 대상 파일이 달라짐).
- **R7**: `verify_session_uuid` 가 미지 에이전트에 대해 `True``False` 로 fail-closed 전환.
둘 다 방향은 옳으나 커밋 메시지·`LOG.md` 어디에도 서술이 없습니다. 차단하지 않되, P3-1 커밋을 최종 확정할 때 한 줄씩 남기기를 권고합니다.
### 문서 — 3라운드 지적 전부 해소 상태 유지 ✅
`IMPROVEMENTS.md` 의 §2/§4/§5 카운트와 머리말 일치, C-3b 의 자기모순 항목 제거, 로드맵 P3-1/P3-3 완료 표기, `LOG.md``## 📌 1.` 헤딩 복원, 격리 잔재 문구 3곳 교정 — 모두 유지되고 있습니다.
---
## 6. 검증 결과
| 항목 | 결과 |
|---|---|
| 전체 회귀 `pytest tests/ -q` | **262 passed in 381.58s (0:06:21)** — 독립 재실행 확인 |
| `test_a4_adapter_contract.py` (`env -u PYTHONPATH`) | **12 passed** — N1 해소 (직전: 1 failed) |
| **뮤테이션 M1** — R1 수정 되돌림 | **12 passed (탐지 실패)** → N2 |
| **뮤테이션 M2**`__main__.py` 서브커맨드 3종 제거 | **1 failed** ✅ 가드 유효 |
| **뮤테이션 M3**`wait_for_tui_ready` 빈 토큰 가드 제거 | **1 failed** ✅ 가드 유효 |
| R1 재현 (브리지 실패 시 위임 키) | 4/4 정상 |
| R2 재현 (코드 주입 페이로드) | 무력 |
| `bash -n` (셸 5종) | 5/5 OK |
| 죽은 import (이번 리팩터 귀속분) | 0건 |
| `resolve_home` 제거 안전성 | 외부 소비자 0건 확인 |
| `_MAM_READY_TOKENS_CLAUDE` | 2회 존치 (N5) |
M3 이 41초 걸린 점도 기록해 둡니다 — 가드를 제거하면 함수가 30회 sleep 루프로 빠지며, 이는 Rev.2 계획서가 예측했던 "크래시가 아니라 30초 오탐 타임아웃" 거동과 정확히 일치합니다.
---
## 7. 한계
- macOS(darwin 25.5.0) 단일 환경. N1 해소는 `env -u PYTHONPATH` 로 확인했을 뿐 실제 CI 러너 실행은 아닙니다.
- 뮤테이션은 격리 클론에서만 수행했고, 각 뮤테이션 후 원본을 복원해 서로 간섭하지 않게 했습니다. 저장소 작업 트리는 리뷰 전후 동일(12 M + 1 ??)합니다.
- `shellcheck` · `pyflakes` 미설치 — 셸은 `bash -n`, Python 미사용 import 는 자체 AST 스캔(보수적).
- hermes 미설치로 해당 어댑터의 `auth_ok`/`discover` 는 계약 테스트로만 확인.
- R2 주입 프로브는 stderr 출력만 하는 비파괴 페이로드입니다.
---
## 8. 결론
3라운드에 걸친 차단 사유가 모두 해소되었습니다.
1. **R1**(위임 키 조용한 오값) — 수정, 재현 검증 완료
2. **R2**(Python 소스 보간 → 조용한 폴백 + 코드 주입) — argv 서브커맨드로 교체, 페이로드 무력화 확인
3. **N1**(회귀 가드가 주변 `PYTHONPATH` 에 의존해 CI 적색) — 수정, 깨끗한 환경에서 12/12 확인
부수 지적 N3·N4 도 정리되었고, 신규 가드 3종 중 2종은 뮤테이션으로 **실제 방어력이 있음을 증명**했습니다. 문서 동기화도 유지되고 있습니다. 어댑터 계층 자체는 1차 리뷰 때부터 견고했고 그대로입니다.
남은 **N2 는 이미 올바른 코드에 대한 회귀 가드가 비어 있는 문제**이지 동작 결함이 아니며, 직전 리뷰에서도 비차단으로 분류한 항목입니다. 지금 와서 차단 사유로 승격하는 것은 기준을 뒤로 옮기는 일이므로 그렇게 하지 않습니다. 다만 "가드가 있는 것처럼 보이는 가드"는 없는 것보다 위험할 수 있으므로 **다음 커밋 전 필수 후속**으로 명시합니다.
설계 변경 요소는 없습니다.
**필수 후속**: N2
**권고**: N5, R6·R7 기록
[VERDICT: PASS]